// Copyright (c) 2024 CHANGLEI. All rights reserved.

import 'dart:math' as math;
import 'dart:math';
import 'dart:ui' as dart_ui;

import 'package:charts_grasp/src/chart/annotation/annotation_settings.dart';
import 'package:charts_grasp/src/chart/axis/axis.dart';
import 'package:charts_grasp/src/chart/axis/category_axis.dart';
import 'package:charts_grasp/src/chart/axis/datetime_axis.dart';
import 'package:charts_grasp/src/chart/axis/datetime_category_axis.dart';
import 'package:charts_grasp/src/chart/axis/logarithmic_axis.dart';
import 'package:charts_grasp/src/chart/axis/numeric_axis.dart';
import 'package:charts_grasp/src/chart/base/chart_base.dart';
import 'package:charts_grasp/src/chart/chart_segment/chart_segment.dart';
import 'package:charts_grasp/src/chart/chart_series/column_series.dart';
import 'package:charts_grasp/src/chart/chart_series/series.dart';
import 'package:charts_grasp/src/chart/chart_series/series_renderer_properties.dart';
import 'package:charts_grasp/src/chart/chart_series/xy_data_series.dart';
import 'package:charts_grasp/src/chart/common/cartesian_state_properties.dart';
import 'package:charts_grasp/src/chart/common/common.dart';
import 'package:charts_grasp/src/chart/common/marker.dart';
import 'package:charts_grasp/src/chart/common/segment_properties.dart';
import 'package:charts_grasp/src/chart/series_painter/area_painter.dart';
import 'package:charts_grasp/src/chart/series_painter/column_painter.dart';
import 'package:charts_grasp/src/chart/series_painter/line_painter.dart';
import 'package:charts_grasp/src/chart/trendlines/trendlines.dart';
import 'package:charts_grasp/src/chart/user_interaction/trackball.dart';
import 'package:charts_grasp/src/chart/utils/enum.dart';
import 'package:charts_grasp/src/circular_chart/utils/helper.dart';
import 'package:charts_grasp/src/common/event_args.dart';
import 'package:charts_grasp/src/common/rendering_details.dart';
import 'package:charts_grasp/src/common/utils/helper.dart';
import 'package:charts_grasp/src/common/utils/typedef.dart';
import 'package:charts_grasp/src/core/core.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';

num? percentageToValue(String? value, num size) {
  if (value != null) {
    return value.contains('%') ? (size / 100) * num.tryParse(value.replaceAll(RegExp('%'), ''))! : num.tryParse(value);
  }
  return null;
}

void drawText(Canvas canvas, String text, Offset point, TextStyle style, [int? angle, bool? isRtl]) {
  final int maxLines = getMaxLinesContent(text);
  final TextSpan span = TextSpan(text: text, style: style);
  final TextPainter tp = TextPainter(
      text: span,
      textDirection: (isRtl ?? false) ? dart_ui.TextDirection.rtl : dart_ui.TextDirection.ltr,
      textAlign: TextAlign.center,
      maxLines: maxLines,);
  tp.layout();
  canvas.save();
  canvas.translate(point.dx, point.dy);
  Offset labelOffset = Offset.zero;
  if (angle != null && angle > 0) {
    canvas.rotate(degreeToRadian(angle));
    labelOffset = Offset(-tp.width / 2, -tp.height / 2);
  }
  tp.paint(canvas, labelOffset);
  canvas.restore();
}

void drawDashedPath(Canvas canvas, CustomPaintStyle style, Offset moveToPoint, Offset lineToPoint,
    [List<double>? dashArray,]) {
  bool even = false;
  final Path path = Path();
  path.moveTo(moveToPoint.dx, moveToPoint.dy);
  path.lineTo(lineToPoint.dx, lineToPoint.dy);
  final Paint paint = Paint();
  paint.strokeWidth = style.strokeWidth;
  paint.color = style.color;
  paint.style = style.paintStyle;

  if (dashArray != null) {
    for (int i = 1; i < dashArray.length; i = i + 2) {
      if (dashArray[i] == 0) {
        even = true;
      }
    }
    if (even == false) {
      canvas.drawPath(
          dashPath(
            path,
            dashArray: CircularIntervalList<double>(dashArray),
          )!,
          paint,);
    }
  } else {
    canvas.drawPath(path, paint);
  }
}

num valueToCoefficient(num? value, ChartAxisRendererDetails axisRendererDetails) {
  num result = 0;
  if (axisRendererDetails.visibleRange != null && value != null) {
    final VisibleRange range = axisRendererDetails.visibleRange!;
      result = (value - range.minimum) / (range.delta);
      result = axisRendererDetails.axis.isInversed ? (1 - result) : result;
  }
  return result;
}

num calculateLogBaseValue(num value, num base) => math.log(value) / math.log(base);

bool withInRange(num value, ChartAxisRendererDetails axisDetails) {
  final ChartAxis axis = axisDetails.axis;
  final num visibleMinimum = axis is LogarithmicAxis
      ? pow(axis.logBase, axisDetails.visibleRange!.minimum)
      : axisDetails.visibleRange!.minimum;
  final num visibleMaximum = axis is LogarithmicAxis
      ? pow(axis.logBase, axisDetails.visibleRange!.maximum)
      : axisDetails.visibleRange!.maximum;
  return (value <= visibleMaximum) && (value >= visibleMinimum);
}

ChartLocation calculatePoint(
    num x,
    num? y,
    ChartAxisRendererDetails xAxisRendererDetails,
    ChartAxisRendererDetails yAxisRendererDetails,
    bool isInverted,
    CartesianSeries<dynamic, dynamic>? series,
    Rect rect,) {
  final ChartAxis xAxis = xAxisRendererDetails.axis;
  final ChartAxis yAxis = yAxisRendererDetails.axis;
  x = xAxis is LogarithmicAxis ? calculateLogBaseValue(x > 0 ? x : 0, xAxis.logBase) : x;
  y = yAxis is LogarithmicAxis
      ? y != null
          ? calculateLogBaseValue(y > 0 ? y : 0, yAxis.logBase)
          : 0
      : y;
  x = valueToCoefficient(x.isInfinite ? 0 : x, xAxisRendererDetails);
  y = valueToCoefficient(
      y != null
          ? y.isInfinite
              ? 0
              : y
          : y,
      yAxisRendererDetails,);
  final num xLength = isInverted ? rect.height : rect.width;
  final num yLength = isInverted ? rect.width : rect.height;
  final double locationX = rect.left + (isInverted ? (y * yLength) : (x * xLength));
  final double locationY = rect.top + (isInverted ? (1 - x) * xLength : (1 - y) * yLength);
  return ChartLocation(locationX, locationY);
}

num calculateMinPointsDelta(ChartAxisRenderer axisRenderer, List<CartesianSeriesRenderer> seriesRenderers,
    CartesianStateProperties stateProperties,) {
  num minDelta = 1.7976931348623157e+308;
  num minVal;
  num? seriesMin;
  dynamic xValues;
  for (final CartesianSeriesRenderer seriesRenderer in seriesRenderers) {
    final SeriesRendererDetails seriesRendererDetails = SeriesHelper.getSeriesRendererDetails(seriesRenderer);
    final CartesianSeries<dynamic, dynamic> series = seriesRendererDetails.series;
    num value;
    xValues = <dynamic>[];
    final String seriesType = seriesRendererDetails.seriesType;
    final bool isRectSeries = seriesType.contains('column') ||
        seriesType.contains('stackedbar') ||
        seriesType == 'bar' ||
        seriesType == 'histogram' ||
        seriesType == 'waterfall' ||
        seriesType.contains('candle') ||
        seriesType.contains('hilo') ||
        seriesType.contains('box');
    final ChartAxisRendererDetails axisRendererDetails = AxisHelper.getAxisRendererDetails(axisRenderer);
    if (seriesRendererDetails.visible! == true &&
        isRectSeries &&
        ((axisRendererDetails.name == series.xAxisName) ||
            (axisRendererDetails.name == (stateProperties.chart.primaryXAxis.name ?? 'primaryXAxis') &&
                series.xAxisName == null) ||
            (axisRendererDetails.name ==
                    AxisHelper.getAxisRendererDetails(stateProperties.chartAxis.primaryXAxisRenderer!).name &&
                series.xAxisName != null))) {
      xValues = axisRenderer is DateTimeAxisRenderer
          ? seriesRendererDetails.overAllDataPoints.map<dynamic>((point) {
              return point!.xValue;
            }).toList()
          : seriesRendererDetails.dataPoints.map<dynamic>((point) {
              return point.xValue;
            }).toList();
      xValues.sort();
      if (xValues.length == 1) {
        DateTime? minDate;
        num? minimumInSeconds;
        if (axisRenderer is DateTimeAxisRenderer) {
          minDate = DateTime.fromMillisecondsSinceEpoch(seriesRendererDetails.minimumX! as int);
          minDate = minDate.subtract(const Duration(days: 1));
          minimumInSeconds = minDate.millisecondsSinceEpoch;
        }
        seriesMin =
            (axisRenderer is DateTimeAxisRenderer && seriesRendererDetails.minimumX == seriesRendererDetails.maximumX)
                ? minimumInSeconds
                : seriesRendererDetails.minimumX;
        minVal = xValues[0] - (seriesMin ?? axisRendererDetails.visibleRange!.minimum);
        if (minVal != 0) {
          minDelta = math.min(minDelta, minVal);
        }
      } else {
        for (int i = 0; i < xValues.length; i++) {
          value = xValues[i];
          if (i > 0) {
            minVal = value - xValues[i - 1];
            if (minVal != 0) {
              minDelta = math.min(minDelta, minVal);
            }
          }
        }
      }
    }
  }
  if (minDelta == 1.7976931348623157e+308) {
    minDelta = 1;
  }

  return minDelta;
}

PaintingStyle calculateLegendShapes(Path path, Rect rect, String seriesType) {
  PaintingStyle style = PaintingStyle.fill;
  switch (seriesType) {
    case 'line':
    case 'fastline':
    case 'stackedline':
    case 'stackedline100':
      style = PaintingStyle.stroke;
      break;
    case 'spline':
      getShapesPath(path: path, rect: rect, shapeType: ShapeMarkerType.splineSeries);
      style = PaintingStyle.stroke;
      break;
    case 'splinearea':
    case 'splinerangearea':
      getShapesPath(path: path, rect: rect, shapeType: ShapeMarkerType.splineAreaSeries);
      break;
    case 'bar':
    case 'stackedbar':
    case 'stackedbar100':
      getShapesPath(path: path, rect: rect, shapeType: ShapeMarkerType.barSeries);
      break;
    case 'column':
    case 'stackedcolumn':
    case 'stackedcolumn100':
    case 'rangecolumn':
    case 'histogram':
      getShapesPath(path: path, rect: rect, shapeType: ShapeMarkerType.columnSeries);
      break;
    case 'area':
    case 'stackedarea':
    case 'rangearea':
    case 'stackedarea100':
      getShapesPath(path: path, rect: rect, shapeType: ShapeMarkerType.areaSeries);
      break;
    case 'stepline':
      getShapesPath(path: path, rect: rect, shapeType: ShapeMarkerType.stepLineSeries);
      style = PaintingStyle.stroke;
      break;
    case 'bubble':
      getShapesPath(path: path, rect: rect, shapeType: ShapeMarkerType.bubbleSeries);
      break;
    case 'hilo':
      getShapesPath(path: path, rect: rect, shapeType: ShapeMarkerType.hiloSeries);
      style = PaintingStyle.stroke;
      break;
    case 'hiloopenclose':
    case 'candle':
      getShapesPath(path: path, rect: rect, shapeType: ShapeMarkerType.candleSeries);
      style = PaintingStyle.stroke;
      break;
    case 'waterfall':
    case 'boxandwhisker':
      getShapesPath(path: path, rect: rect, shapeType: ShapeMarkerType.waterfallSeries);
      break;
    case 'pie':
      getShapesPath(path: path, rect: rect, shapeType: ShapeMarkerType.pieSeries);
      break;
    case 'doughnut':
    case 'radialbar':
      break;
    case 'steparea':
      getShapesPath(path: path, rect: rect, shapeType: ShapeMarkerType.stepAreaSeries);
      break;
    case 'pyramid':
      getShapesPath(path: path, rect: rect, shapeType: ShapeMarkerType.pyramidSeries);
      break;
    case 'funnel':
      getShapesPath(path: path, rect: rect, shapeType: ShapeMarkerType.funnelSeries);
      break;
    default:
      path.addArc(
          Rect.fromLTRB(rect.left - rect.width / 2, rect.top - rect.height / 2, rect.left + rect.width / 2,
              rect.top + rect.height / 2,),
          0,
          2 * math.pi,);
      break;
  }
  return style;
}

void calculateBarTypeIconPath(Path path, double x, double y, double width, double height) {
  const num padding = 10;
  path.moveTo(x - (width / 2) - padding / 4, y - 3 * (height / 5));
  path.lineTo(x + 3 * (width / 10), y - 3 * (height / 5));
  path.lineTo(x + 3 * (width / 10), y - 3 * (height / 10));
  path.lineTo(x - (width / 2) - padding / 4, y - 3 * (height / 10));
  path.close();
  path.moveTo(x - (width / 2) - (padding / 4), y - (height / 5) + (padding / 20));
  path.lineTo(x + (width / 2) + (padding / 4), y - (height / 5) + (padding / 20));
  path.lineTo(x + (width / 2) + (padding / 4), y + (height / 10) + (padding / 20));
  path.lineTo(x - (width / 2) - (padding / 4), y + (height / 10) + (padding / 20));
  path.close();
  path.moveTo(x - (width / 2) - (padding / 4), y + (height / 5) + (padding / 10));
  path.lineTo(x - width / 4, y + (height / 5) + (padding / 10));
  path.lineTo(x - width / 4, y + (height / 2) + (padding / 10));
  path.lineTo(x - (width / 2) - (padding / 4), y + (height / 2) + (padding / 10));
  path.close();
}

void calculateColumnTypeIconPath(Path path, double x, double y, double width, double height) {
  const num padding = 10;
  path.moveTo(x - 3 * (width / 5), y - (height / 5));
  path.lineTo(x + 3 * (-width / 10), y - (height / 5));
  path.lineTo(x + 3 * (-width / 10), y + (height / 2));
  path.lineTo(x - 3 * (width / 5), y + (height / 2));
  path.close();
  path.moveTo(x - (width / 10) - (width / 20), y - (height / 4) - (padding / 2));
  path.lineTo(x + (width / 10) + (width / 20), y - (height / 4) - (padding / 2));
  path.lineTo(x + (width / 10) + (width / 20), y + (height / 2));
  path.lineTo(x - (width / 10) - (width / 20), y + (height / 2));
  path.close();
  path.moveTo(x + 3 * (width / 10), y);
  path.lineTo(x + 3 * (width / 5), y);
  path.lineTo(x + 3 * (width / 5), y + (height / 2));
  path.lineTo(x + 3 * (width / 10), y + (height / 2));
  path.close();
}

void calculateAreaTypeIconPath(Path path, double x, double y, double width, double height) {
  const num padding = 10;
  path.moveTo(x - (width / 2) - (padding / 4), y + (height / 2));
  path.lineTo(x - (width / 4) - (padding / 8), y - (height / 2));
  path.lineTo(x, y + (height / 4));
  path.lineTo(x + (width / 4) + (padding / 8), y - (height / 2) + (height / 4));
  path.lineTo(x + (height / 2) + (padding / 4), y + (height / 2));
  path.close();
}

void calculateSteplineIconPath(Path path, double x, double y, double width, double height) {
  const num padding = 10;
  path.moveTo(x - (width / 2) - (padding / 4), y + (height / 2));
  path.lineTo(x - (width / 2) + (width / 10), y + (height / 2));
  path.lineTo(x - (width / 2) + (width / 10), y);
  path.lineTo(x - (width / 10), y);
  path.lineTo(x - (width / 10), y + (height / 2));
  path.lineTo(x + (width / 5), y + (height / 2));
  path.lineTo(x + (width / 5), y - (height / 2));
  path.lineTo(x + (width / 2), y - (height / 2));
  path.lineTo(x + (width / 2), y + (height / 2));
  path.lineTo(x + (width / 2) + (padding / 4), y + (height / 2));
}

void calculateStepAreaIconPath(Path path, double x, double y, double width, double height) {
  const num padding = 10;
  path.moveTo(x - (width / 2) - (padding / 4), y + (height / 2));
  path.lineTo(x - (width / 2) - (padding / 4), y - (height / 4));
  path.lineTo(x - (width / 2) + (width / 10), y - (height / 4));
  path.lineTo(x - (width / 2) + (width / 10), y - (height / 2));
  path.lineTo(x - (width / 10), y - (height / 2));
  path.lineTo(x - (width / 10), y);
  path.lineTo(x + (width / 5), y);
  path.lineTo(x + (width / 5), y - (height / 3));
  path.lineTo(x + (width / 2), y - (height / 3));
  path.lineTo(x + (width / 2), y + (height / 2));
  path.close();
}

void calculatePieIconPath(Path path, double x, double y, double width, double height) {
  final double r = math.min(height, width) / 2;
  path.moveTo(x, y);
  path.lineTo(x + r, y);
  path.arcTo(Rect.fromCircle(center: Offset(x, y), radius: r), degreesToRadians(0).toDouble(),
      degreesToRadians(270).toDouble(), false,);
  path.close();
  path.moveTo(x + width / 10, y - height / 10);
  path.lineTo(x + r, y - height / 10);
  path.arcTo(Rect.fromCircle(center: Offset(x + 2, y - 2), radius: r), degreesToRadians(-5).toDouble(),
      degreesToRadians(-80).toDouble(), false,);
  path.close();
}

void calculatePyramidIconPath(Path path, double x, double y, double width, double height) {
  path.moveTo(x - width / 2, y + height / 2);
  path.lineTo(x + width / 2, y + height / 2);
  path.lineTo(x, y - height / 2);
  path.lineTo(x - width / 2, y + height / 2);
  path.close();
}

void calculateFunnelIconPath(Path path, double x, double y, double width, double height) {
  path.moveTo(x + width / 2, y - height / 2);
  path.lineTo(x, y + height / 2);
  path.lineTo(x - width / 2, y - height / 2);
  path.lineTo(x + width / 2, y - height / 2);
  path.close();
}

Rect calculateRectangle(num x1, num? y1, num x2, num? y2, CartesianSeriesRenderer seriesRenderer,
    CartesianStateProperties stateProperties,) {
  final SeriesRendererDetails seriesRendererDetails = SeriesHelper.getSeriesRendererDetails(seriesRenderer);
  final Rect rect = calculatePlotOffset(stateProperties.chartAxis.axisClipRect,
      Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, seriesRendererDetails.yAxisDetails!.axis.plotOffset),);
  final bool isInverted = stateProperties.requireInvertedAxis;
  final ChartLocation point1 = calculatePoint(x1, y1, seriesRendererDetails.xAxisDetails!,
      seriesRendererDetails.yAxisDetails!, isInverted, seriesRendererDetails.series, rect,);
  final ChartLocation point2 = calculatePoint(x2, y2, seriesRendererDetails.xAxisDetails!,
      seriesRendererDetails.yAxisDetails!, isInverted, seriesRendererDetails.series, rect,);
  return Rect.fromLTWH(math.min(point1.x, point2.x), math.min(point1.y, point2.y), (point2.x - point1.x).abs(),
      (point2.y - point1.y).abs(),);
}

Rect calculateShadowRectangle(num x1, num y1, num x2, num y2, CartesianSeriesRenderer seriesRenderer,
    CartesianStateProperties stateProperties, Offset plotOffset,) {
  final SeriesRendererDetails seriesRendererDetails = SeriesHelper.getSeriesRendererDetails(seriesRenderer);
  final Rect rect = calculatePlotOffset(stateProperties.chartAxis.axisClipRect,
      Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, seriesRendererDetails.yAxisDetails!.axis.plotOffset),);
  final bool isInverted = stateProperties.requireInvertedAxis;
  final ChartLocation point1 = calculatePoint(x1, y1, seriesRendererDetails.xAxisDetails!,
      seriesRendererDetails.yAxisDetails!, isInverted, seriesRendererDetails.series, rect,);
  final ChartLocation point2 = calculatePoint(x2, y2, seriesRendererDetails.xAxisDetails!,
      seriesRendererDetails.yAxisDetails!, isInverted, seriesRendererDetails.series, rect,);
  final bool isColumn = seriesRendererDetails.seriesType == 'column';
  ColumnSeries<dynamic, dynamic>? columnSeries;
  if (seriesRendererDetails.seriesType == 'column') {
    columnSeries = seriesRendererDetails.series as ColumnSeries<dynamic, dynamic>;
  }
  return !stateProperties.chart.isTransposed
      ? _getNormalShadowRect(rect, isColumn, stateProperties, plotOffset, point1, point2, columnSeries)
      : _getTransposedShadowRect(rect, isColumn, stateProperties, plotOffset, point1, point2, columnSeries);
}

Rect _getNormalShadowRect(
  Rect rect,
  bool isColumn,
  CartesianStateProperties stateProperties,
  Offset plotOffset,
  ChartLocation point1,
  ChartLocation point2,
  ColumnSeries<dynamic, dynamic>? columnSeries,
) {
  return Rect.fromLTWH(
    isColumn
        ? math.min(point1.x, point2.x) + (-columnSeries!.trackBorderWidth - columnSeries.trackPadding)
        : stateProperties.chartAxis.axisClipRect.left,
    rect.top,
    isColumn
        ? (point2.x - point1.x).abs() + (columnSeries!.trackBorderWidth * 2) + columnSeries.trackPadding * 2
        : stateProperties.chartAxis.axisClipRect.width,
    stateProperties.chartAxis.axisClipRect.height - 2 * plotOffset.dy,
  );
}

Rect _getTransposedShadowRect(
  Rect rect,
  bool isColumn,
  CartesianStateProperties stateProperties,
  Offset plotOffset,
  ChartLocation point1,
  ChartLocation point2,
  ColumnSeries<dynamic, dynamic>? columnSeries,
) {
  return Rect.fromLTWH(
    stateProperties.chartAxis.axisClipRect.left,
    isColumn ? (math.min(point1.y, point2.y) - columnSeries!.trackBorderWidth - columnSeries.trackPadding) : rect.top,
    stateProperties.chartAxis.axisClipRect.width,
    isColumn
        ? ((point2.y - point1.y).abs() + columnSeries!.trackBorderWidth * 2 + columnSeries.trackPadding * 2)
        : (stateProperties.chartAxis.axisClipRect.height - 2 * plotOffset.dy),
  );
}

VisibleRange calculateSideBySideInfo(CartesianSeriesRenderer seriesRenderer, CartesianStateProperties stateProperties) {
  num? rectPosition;
  num? count;
  num seriesSpacing;
  num? pointSpacing;
  final CgCartesianChart chart = stateProperties.chart;
  final SeriesRendererDetails seriesRendererDetails = SeriesHelper.getSeriesRendererDetails(seriesRenderer);
  final CartesianSeries<dynamic, dynamic> series = seriesRendererDetails.series;
  if (seriesRendererDetails.seriesType == 'column' && chart.enableSideBySideSeriesPlacement) {
    final ColumnSeriesRenderer columnSeriesRenderer = seriesRenderer as ColumnSeriesRenderer;
    _calculateSideBySidePositions(columnSeriesRenderer, stateProperties);
    rectPosition = seriesRendererDetails.rectPosition;
    count = seriesRendererDetails.isIndicator
        ? stateProperties.sideBySideIndicatorCount
        : stateProperties.sideBySideSeriesCount;
  }

  final ColumnSeries<dynamic, dynamic> columnSeries = series as ColumnSeries<dynamic, dynamic>;
  seriesSpacing = chart.enableSideBySideSeriesPlacement ? columnSeries.spacing : 0;
  assert(columnSeries.width == null || columnSeries.width! <= 1,
      'The width of the column series must be less than or equal 1.',);
  // ignore: unnecessary_null_checks
  pointSpacing = columnSeries.width!;
  final num position = !chart.enableSideBySideSeriesPlacement ? 0 : rectPosition!;
  final num rectCount = !chart.enableSideBySideSeriesPlacement ? 1 : count!;

  final num minPointsDelta = seriesRendererDetails.minDelta ??
      calculateMinPointsDelta(
          seriesRendererDetails.xAxisDetails!.axisRenderer, stateProperties.seriesRenderers, stateProperties,);
  final num width = minPointsDelta * pointSpacing;
  final num location = position / rectCount - 0.5;
  VisibleRange doubleRange = VisibleRange(location, location + (1 / rectCount));

  if ((doubleRange.minimum is num) && (doubleRange.maximum is num)) {
    doubleRange = VisibleRange(doubleRange.minimum * width, doubleRange.maximum * width);
    doubleRange.delta = doubleRange.maximum - doubleRange.minimum;
    final num radius = seriesSpacing * doubleRange.delta;
    doubleRange = VisibleRange(doubleRange.minimum + radius / 2, doubleRange.maximum - radius / 2);
    doubleRange.delta = doubleRange.maximum - doubleRange.minimum;
  }
  return doubleRange;
}

ChartLocation getRotatedTextLocation(
    double pointX, double pointY, String labelText, TextStyle textStyle, int angle, ChartAxis axis,) {
  if (angle > 0) {
    final Size textSize = measureText(labelText, textStyle);
    final Size rotateTextSize = measureText(labelText, textStyle, angle);

    pointX +=
        ((rotateTextSize.width - textSize.width).abs() / 2) + (((angle > 90 ? 90 : angle) / 90) * textSize.height);

    pointX += (angle > 90) ? (rotateTextSize.width - textSize.height).abs() : 0;
    pointY += (angle > 90) ? (angle / 180) * textSize.height - (((180 - angle) / 180) * textSize.height) : 0;

    pointX -= (angle > 180) ? (angle / 270) * textSize.height : 0;
    pointY += (angle > 180) ? (rotateTextSize.height - textSize.height).abs() - (angle / 270) * textSize.height : 0;

    pointX -= (angle > 270) ? (rotateTextSize.width - textSize.height).abs() : 0;
    pointY -=
        (angle > 270) ? (((angle - 270) / 90) * textSize.height) + (textSize.height * ((angle - 270) / 90)) / 2 : 0;
    if (axis.labelRotation.isNegative) {
      final num rotation = axis.labelRotation.abs();
      if (rotation > 15 && rotation < 90) {
        pointX -= (rotateTextSize.width - textSize.height).abs() / 2;
        pointY -= ((90 - rotation) / 90) / 2 * textSize.height;
      } else if (rotation > 90 && rotation < 180) {
        pointX += rotation > 164
            ? 0
            : (rotateTextSize.width - textSize.height).abs() / 2 + ((rotation - 90) / 90) / 2 * textSize.height;
        pointY += (rotation / 180) / 2 * textSize.height;
      } else if (rotation > 195 && rotation < 270) {
        pointX -= (rotateTextSize.width - textSize.height).abs() / 2;
      } else if (rotation > 270 && rotation < 360) {
        pointX += (rotateTextSize.width - textSize.height).abs() / 2;
      }
    }
  }
  return ChartLocation(pointX, pointY);
}

bool isSameSeries(dynamic oldSeries, dynamic newSeries) {
  return oldSeries.runtimeType == newSeries.runtimeType && oldSeries.key == newSeries.key;
}

void _calculateSideBySidePositions(CartesianSeriesRenderer seriesRenderer, CartesianStateProperties stateProperties) {
  final List<CartesianSeriesRenderer> seriesCollection = _findRectSeriesCollection(stateProperties);
  int rectCount = 0;
  int indicatorPosition = 0;
  for (final dynamic seriesRenderer in seriesCollection) {
    if (seriesRenderer is ColumnSeriesRenderer) {
      final SeriesRendererDetails seriesRendererDetails = SeriesHelper.getSeriesRendererDetails(seriesRenderer);
      seriesRendererDetails.rectPosition = seriesRendererDetails.isIndicator ? indicatorPosition++ : rectCount++;
    }
  }
}

List<CartesianSeriesRenderer> findSeriesCollection(CartesianStateProperties stateProperties, [bool? isRect]) {
  final List<CartesianSeriesRenderer> seriesRendererCollection = <CartesianSeriesRenderer>[];
  for (int xAxisIndex = 0; xAxisIndex < stateProperties.chartAxis.horizontalAxisRenderers.length; xAxisIndex++) {
    final ChartAxisRenderer xAxisRenderer = stateProperties.chartAxis.horizontalAxisRenderers[xAxisIndex];
    final ChartAxisRendererDetails xAxisRendererDetails = AxisHelper.getAxisRendererDetails(xAxisRenderer);
    for (int xSeriesIndex = 0; xSeriesIndex < xAxisRendererDetails.seriesRenderers.length; xSeriesIndex++) {
      final CartesianSeriesRenderer xAxisSeriesRenderer = xAxisRendererDetails.seriesRenderers[xSeriesIndex];
      for (int yAxisIndex = 0; yAxisIndex < stateProperties.chartAxis.verticalAxisRenderers.length; yAxisIndex++) {
        final ChartAxisRenderer yAxisRenderer = stateProperties.chartAxis.verticalAxisRenderers[yAxisIndex];
        final ChartAxisRendererDetails yAxisRendererDetails = AxisHelper.getAxisRendererDetails(yAxisRenderer);
        for (int ySeriesIndex = 0; ySeriesIndex < yAxisRendererDetails.seriesRenderers.length; ySeriesIndex++) {
          final CartesianSeriesRenderer yAxisSeriesRenderer = yAxisRendererDetails.seriesRenderers[ySeriesIndex];
          final SeriesRendererDetails seriesRendererDetails =
              SeriesHelper.getSeriesRendererDetails(xAxisSeriesRenderer);
          if (xAxisSeriesRenderer == yAxisSeriesRenderer &&
              (seriesRendererDetails.seriesType.contains('column') == true ||
                  seriesRendererDetails.seriesType.contains('bar') == true ||
                  seriesRendererDetails.seriesType.contains('hilo') == true ||
                  seriesRendererDetails.seriesType.contains('candle') == true ||
                  seriesRendererDetails.seriesType.contains('stackedarea') == true ||
                  seriesRendererDetails.seriesType.contains('stackedline') == true ||
                  seriesRendererDetails.seriesType == 'histogram' ||
                  seriesRendererDetails.seriesType == 'boxandwhisker') &&
              seriesRendererDetails.visible! == true) {
            if (!seriesRendererCollection.contains(yAxisSeriesRenderer)) {
              seriesRendererCollection.add(yAxisSeriesRenderer);
            }
          }
        }
      }
    }
  }
  return seriesRendererCollection;
}

RRect getRRectFromRect(Rect rect, BorderRadius borderRadius) {
  return RRect.fromRectAndCorners(
    rect,
    bottomLeft: borderRadius.bottomLeft,
    bottomRight: borderRadius.bottomRight,
    topLeft: borderRadius.topLeft,
    topRight: borderRadius.topRight,
  );
}

List<CartesianSeriesRenderer> _findRectSeriesCollection(CartesianStateProperties stateProperties) {
  int rectSeriesCount = 0;
  int rectIndicatorCount = 0;
  final List<CartesianSeriesRenderer> seriesRenderCollection = <CartesianSeriesRenderer>[];
  for (int xAxisIndex = 0; xAxisIndex < stateProperties.chartAxis.horizontalAxisRenderers.length; xAxisIndex++) {
    final ChartAxisRenderer xAxisRenderer = stateProperties.chartAxis.horizontalAxisRenderers[xAxisIndex];
    final ChartAxisRendererDetails xAxisRendererDetails = AxisHelper.getAxisRendererDetails(xAxisRenderer);
    for (int xSeriesIndex = 0; xSeriesIndex < xAxisRendererDetails.seriesRenderers.length; xSeriesIndex++) {
      final CartesianSeriesRenderer xAxisSeriesRenderer = xAxisRendererDetails.seriesRenderers[xSeriesIndex];
      for (int yAxisIndex = 0; yAxisIndex < stateProperties.chartAxis.verticalAxisRenderers.length; yAxisIndex++) {
        final ChartAxisRenderer yAxisRenderer = stateProperties.chartAxis.verticalAxisRenderers[yAxisIndex];
        final ChartAxisRendererDetails yAxisRendererDetails = AxisHelper.getAxisRendererDetails(yAxisRenderer);
        for (int ySeriesIndex = 0; ySeriesIndex < yAxisRendererDetails.seriesRenderers.length; ySeriesIndex++) {
          final CartesianSeriesRenderer yAxisSeriesRenderer = yAxisRendererDetails.seriesRenderers[ySeriesIndex];
          final SeriesRendererDetails seriesRendererDetails =
              SeriesHelper.getSeriesRendererDetails(xAxisSeriesRenderer);
          if (xAxisSeriesRenderer == yAxisSeriesRenderer &&
              (seriesRendererDetails.seriesType.contains('column') == true ||
                  seriesRendererDetails.seriesType.contains('waterfall') == true ||
                  (seriesRendererDetails.seriesType.contains('bar') == true &&
                      !seriesRendererDetails.seriesType.contains('errorbar')) ||
                  seriesRendererDetails.seriesType.contains('hilo') == true ||
                  seriesRendererDetails.seriesType == 'candle' ||
                  seriesRendererDetails.seriesType == 'histogram' ||
                  seriesRendererDetails.seriesType == 'boxandwhisker') &&
              seriesRendererDetails.visible! == true) {
            if (!seriesRenderCollection.contains(yAxisSeriesRenderer)) {
              seriesRenderCollection.add(yAxisSeriesRenderer);
              if (seriesRendererDetails.isIndicator) {
                rectIndicatorCount++;
              } else {
                rectSeriesCount++;
              }
            }
          }
        }
      }
    }
  }
  stateProperties.sideBySideSeriesCount = rectSeriesCount;
  stateProperties.sideBySideIndicatorCount = rectIndicatorCount;
  return seriesRenderCollection;
}

Rect calculatePlotOffset(Rect axisClipRect, Offset plotOffset) => Rect.fromLTWH(axisClipRect.left + plotOffset.dx,
    axisClipRect.top + plotOffset.dy, axisClipRect.width - 2 * plotOffset.dx, axisClipRect.height - 2 * plotOffset.dy,);

Paint getLinearGradientPaint(LinearGradient gradientFill, Rect region, bool isInvertedAxis) {
  Paint gradientPaint;
  gradientPaint = Paint()
    ..shader = gradientFill.createShader(region)
    ..style = PaintingStyle.fill;
  return gradientPaint;
}

Paint getShaderPaint(Shader shader) {
  Paint shaderPaint;
  shaderPaint = Paint()
    ..shader = shader
    ..style = PaintingStyle.fill;
  return shaderPaint;
}

String getLabelValue(dynamic value, dynamic axis, [int? showDigits]) {
  if (value.toString().split('.').length > 1) {
    final String str = value.toString();
    final List<dynamic> list = str.split('.');
    value = double.parse(value.toStringAsFixed(showDigits ?? 3));
    value = (list[1] == '0' ||
            list[1] == '00' ||
            list[1] == '000' ||
            list[1] == '0000' ||
            list[1] == '00000' ||
            list[1] == '000000' ||
            list[1] == '0000000')
        ? value.round()
        : value;
  }
  final dynamic text = axis is NumericAxis && axis.numberFormat != null ? axis.numberFormat!.format(value) : value;
  return ((axis.labelFormat != null && axis.labelFormat != '')
      ? axis.labelFormat.replaceAll(RegExp('{value}'), text.toString())
      : text.toString()) as String;
}

double pointToXValue(bool requireInvertedAxis, ChartAxisRenderer axisRenderer, Rect rect, double x, double y) {
  if (!requireInvertedAxis) {
    return _coefficientToValue(x / rect.width, axisRenderer);
  }
  return _coefficientToValue(1 - (y / rect.height), axisRenderer);
}

double pointToYValue(bool requireInvertedAxis, ChartAxisRenderer axisRenderer, Rect rect, double x, double y) {
  if (!requireInvertedAxis) {
    return _coefficientToValue(1 - (y / rect.height), axisRenderer);
  }
  return _coefficientToValue(x / rect.width, axisRenderer);
}

double _coefficientToValue(double coefficient, ChartAxisRenderer axisRenderer) {
  double result;
  final ChartAxisRendererDetails axisRendererDetails = AxisHelper.getAxisRendererDetails(axisRenderer);
  coefficient = axisRendererDetails.axis.isInversed ? 1 - coefficient : coefficient;
  result = axisRendererDetails.visibleRange!.minimum + (axisRendererDetails.visibleRange!.delta * coefficient);
  return result;
}

void needsRepaintChart(CartesianStateProperties stateProperties, List<ChartAxisRenderer> oldChartAxisRenderers,
    List<CartesianSeriesRenderer> oldChartSeriesRenderers,) {
  if (stateProperties.chartSeries.visibleSeriesRenderers.length == oldChartSeriesRenderers.length) {
    for (int seriesIndex = 0; seriesIndex < stateProperties.seriesRenderers.length; seriesIndex++) {
      _canRepaintChartSeries(stateProperties, oldChartSeriesRenderers, seriesIndex);
    }
  } else {
    for (final seriesRenderer in stateProperties.seriesRenderers) {
      SeriesHelper.getSeriesRendererDetails(seriesRenderer).needsRepaint = true;
    }
  }
  if (stateProperties.chartAxis.axisRenderersCollection.length == oldChartAxisRenderers.length) {
    for (int axisIndex = 0; axisIndex < oldChartAxisRenderers.length; axisIndex++) {
      _canRepaintAxis(stateProperties, oldChartAxisRenderers, axisIndex);
      if (stateProperties.chartAxis.needsRepaint) {
        break;
      }
    }
  } else {
    stateProperties.chartAxis.needsRepaint = true;
  }
}

void _canRepaintChartSeries(
    CartesianStateProperties stateProperties, List<CartesianSeriesRenderer> oldChartSeriesRenderers, int seriesIndex,) {
  final CartesianSeriesRenderer seriesRenderer = stateProperties.seriesRenderers[seriesIndex];
  final SeriesRendererDetails seriesRendererDetails = SeriesHelper.getSeriesRendererDetails(seriesRenderer);
  final CartesianSeries<dynamic, dynamic> series = seriesRendererDetails.series;
  final CartesianSeriesRenderer oldWidgetSeriesRenderer = oldChartSeriesRenderers[seriesIndex];
  final SeriesRendererDetails oldSeriesRendererDetails = SeriesHelper.getSeriesRendererDetails(oldWidgetSeriesRenderer);
  final CartesianSeries<dynamic, dynamic> oldWidgetSeries = oldSeriesRendererDetails.series;
  if (series.animationDuration != oldWidgetSeries.animationDuration ||
      oldSeriesRendererDetails.stateProperties.chartSeries != stateProperties.chartSeries ||
      series.color?.value != oldWidgetSeries.color?.value ||
      series.width != oldWidgetSeries.width ||
      series.isVisible != oldWidgetSeries.isVisible ||
      series.enableTooltip != oldWidgetSeries.enableTooltip ||
      series.name != oldWidgetSeries.name ||
      series.gradient != oldWidgetSeries.gradient ||
      seriesRendererDetails.xAxisDetails!.visibleRange?.delta !=
          oldSeriesRendererDetails.xAxisDetails!.visibleRange?.delta ||
      seriesRendererDetails.xAxisDetails!.visibleRange?.maximum !=
          oldSeriesRendererDetails.xAxisDetails!.visibleRange?.maximum ||
      seriesRendererDetails.xAxisDetails!.visibleRange?.minimum !=
          oldSeriesRendererDetails.xAxisDetails!.visibleRange?.minimum ||
      seriesRendererDetails.xAxisDetails!.visibleRange?.interval !=
          oldSeriesRendererDetails.xAxisDetails!.visibleRange?.interval ||
      seriesRendererDetails.xAxisDetails!.axis.isVisible != oldSeriesRendererDetails.xAxisDetails!.axis.isVisible ||
      seriesRendererDetails.xAxisDetails!.bounds != oldSeriesRendererDetails.xAxisDetails!.bounds ||
      seriesRendererDetails.xAxisDetails!.axis.isInversed != oldSeriesRendererDetails.xAxisDetails!.axis.isInversed ||
      seriesRendererDetails.xAxisDetails!.axis.desiredIntervals !=
          oldSeriesRendererDetails.xAxisDetails!.axis.desiredIntervals ||
      seriesRendererDetails.xAxisDetails!.axis.enableAutoIntervalOnZooming !=
          oldSeriesRendererDetails.xAxisDetails!.axis.enableAutoIntervalOnZooming ||
      seriesRendererDetails.xAxisDetails!.axis.opposedPosition !=
          oldSeriesRendererDetails.xAxisDetails!.axis.opposedPosition ||
      seriesRendererDetails.xAxisDetails!.orientation != oldSeriesRendererDetails.xAxisDetails!.orientation ||
      seriesRendererDetails.xAxisDetails!.axis.plotOffset != oldSeriesRendererDetails.xAxisDetails!.axis.plotOffset ||
      seriesRendererDetails.xAxisDetails!.axis.rangePadding !=
          oldSeriesRendererDetails.xAxisDetails!.axis.rangePadding ||
      seriesRendererDetails.dataPoints.length != oldSeriesRendererDetails.dataPoints.length ||
      seriesRendererDetails.yAxisDetails!.visibleRange?.delta !=
          oldSeriesRendererDetails.yAxisDetails!.visibleRange?.delta ||
      seriesRendererDetails.yAxisDetails!.visibleRange?.maximum !=
          oldSeriesRendererDetails.yAxisDetails!.visibleRange?.maximum ||
      seriesRendererDetails.yAxisDetails!.visibleRange?.minimum !=
          oldSeriesRendererDetails.yAxisDetails!.visibleRange?.minimum ||
      seriesRendererDetails.yAxisDetails!.visibleRange?.interval !=
          oldSeriesRendererDetails.yAxisDetails!.visibleRange?.interval ||
      seriesRendererDetails.yAxisDetails!.axis.isVisible != oldSeriesRendererDetails.yAxisDetails!.axis.isVisible ||
      seriesRendererDetails.yAxisDetails!.bounds != oldSeriesRendererDetails.yAxisDetails!.bounds ||
      seriesRendererDetails.yAxisDetails!.axis.isInversed != oldSeriesRendererDetails.yAxisDetails!.axis.isInversed ||
      seriesRendererDetails.yAxisDetails!.axis.desiredIntervals !=
          oldSeriesRendererDetails.yAxisDetails!.axis.desiredIntervals ||
      seriesRendererDetails.yAxisDetails!.axis.enableAutoIntervalOnZooming !=
          oldSeriesRendererDetails.yAxisDetails!.axis.enableAutoIntervalOnZooming ||
      seriesRendererDetails.yAxisDetails!.axis.opposedPosition !=
          oldSeriesRendererDetails.yAxisDetails!.axis.opposedPosition ||
      seriesRendererDetails.yAxisDetails!.orientation != oldSeriesRendererDetails.yAxisDetails!.orientation ||
      seriesRendererDetails.yAxisDetails!.axis.plotOffset != oldSeriesRendererDetails.yAxisDetails!.axis.plotOffset ||
      seriesRendererDetails.yAxisDetails!.axis.rangePadding !=
          oldSeriesRendererDetails.yAxisDetails!.axis.rangePadding ||
      series.animationDuration != oldWidgetSeries.animationDuration ||
      series.borderColor != oldWidgetSeries.borderColor ||
      series.borderWidth != oldWidgetSeries.borderWidth ||
      series.sizeValueMapper != oldWidgetSeries.sizeValueMapper ||
      series.emptyPointSettings.borderWidth != oldWidgetSeries.emptyPointSettings.borderWidth ||
      series.emptyPointSettings.borderColor != oldWidgetSeries.emptyPointSettings.borderColor ||
      series.emptyPointSettings.color != oldWidgetSeries.emptyPointSettings.color ||
      series.emptyPointSettings.mode != oldWidgetSeries.emptyPointSettings.mode ||
      seriesRendererDetails.maximumX != oldSeriesRendererDetails.maximumX ||
      seriesRendererDetails.maximumY != oldSeriesRendererDetails.maximumY ||
      seriesRendererDetails.minimumX != oldSeriesRendererDetails.minimumX ||
      seriesRendererDetails.minimumY != oldSeriesRendererDetails.minimumY ||
      series.dashArray.length != oldWidgetSeries.dashArray.length ||
      series.dataSource.length != oldWidgetSeries.dataSource.length ||
      series.markerSettings.width != oldWidgetSeries.markerSettings.width ||
      series.markerSettings.color?.value != oldWidgetSeries.markerSettings.color?.value ||
      series.markerSettings.borderColor?.value != oldWidgetSeries.markerSettings.borderColor?.value ||
      series.markerSettings.isVisible != oldWidgetSeries.markerSettings.isVisible ||
      series.markerSettings.borderWidth != oldWidgetSeries.markerSettings.borderWidth ||
      series.markerSettings.height != oldWidgetSeries.markerSettings.height ||
      series.markerSettings.shape != oldWidgetSeries.markerSettings.shape ||
      series.dataLabelSettings.color?.value != oldWidgetSeries.dataLabelSettings.color?.value ||
      series.dataLabelSettings.isVisible != oldWidgetSeries.dataLabelSettings.isVisible ||
      series.dataLabelSettings.labelAlignment != oldWidgetSeries.dataLabelSettings.labelAlignment ||
      series.dataLabelSettings.opacity != oldWidgetSeries.dataLabelSettings.opacity ||
      series.dataLabelSettings.alignment != oldWidgetSeries.dataLabelSettings.alignment ||
      series.dataLabelSettings.angle != oldWidgetSeries.dataLabelSettings.angle ||
      (series.dataLabelSettings.textStyle != null &&
          (series.dataLabelSettings.textStyle?.color?.value !=
                  oldWidgetSeries.dataLabelSettings.textStyle?.color?.value ||
              series.dataLabelSettings.textStyle?.fontStyle != oldWidgetSeries.dataLabelSettings.textStyle?.fontStyle ||
              series.dataLabelSettings.textStyle?.fontFamily !=
                  oldWidgetSeries.dataLabelSettings.textStyle?.fontFamily ||
              series.dataLabelSettings.textStyle?.fontSize != oldWidgetSeries.dataLabelSettings.textStyle?.fontSize ||
              series.dataLabelSettings.textStyle?.fontWeight !=
                  oldWidgetSeries.dataLabelSettings.textStyle?.fontWeight)) ||
      series.dataLabelSettings.borderColor.value != oldWidgetSeries.dataLabelSettings.borderColor.value ||
      series.dataLabelSettings.borderWidth != oldWidgetSeries.dataLabelSettings.borderWidth ||
      series.dataLabelSettings.margin.right != oldWidgetSeries.dataLabelSettings.margin.right ||
      series.dataLabelSettings.margin.bottom != oldWidgetSeries.dataLabelSettings.margin.bottom ||
      series.dataLabelSettings.margin.top != oldWidgetSeries.dataLabelSettings.margin.top ||
      series.dataLabelSettings.margin.left != oldWidgetSeries.dataLabelSettings.margin.left ||
      series.dataLabelSettings.borderRadius != oldWidgetSeries.dataLabelSettings.borderRadius) {
    seriesRendererDetails.needsRepaint = true;
  } else {
    seriesRendererDetails.needsRepaint = false;
  }
}

void _canRepaintAxis(
    CartesianStateProperties stateProperties, List<ChartAxisRenderer> oldChartAxisRenderers, int axisIndex,) {
  if (stateProperties.chartAxis.axisRenderersCollection.isNotEmpty) {
    final ChartAxisRenderer axisRenderer = stateProperties.chartAxis.axisRenderersCollection[axisIndex];
    final ChartAxisRenderer oldWidgetAxisRenderer = oldChartAxisRenderers[axisIndex];
    final ChartAxisRendererDetails axisRendererDetails = AxisHelper.getAxisRendererDetails(axisRenderer);
    final ChartAxisRendererDetails oldAxisRendererDetails = AxisHelper.getAxisRendererDetails(oldWidgetAxisRenderer);
    final ChartAxis axis = axisRendererDetails.axis;
    final ChartAxis oldWidgetAxis = oldAxisRendererDetails.axis;
    if (axis.rangePadding.index != oldWidgetAxis.rangePadding.index ||
        axis.plotOffset != oldWidgetAxis.plotOffset ||
        axisRendererDetails.orientation != oldAxisRendererDetails.orientation ||
        axis.opposedPosition != oldWidgetAxis.opposedPosition ||
        axis.minorTicksPerInterval != oldWidgetAxis.minorTicksPerInterval ||
        axis.desiredIntervals != oldWidgetAxis.desiredIntervals ||
        axis.isInversed != oldWidgetAxis.isInversed ||
        axisRendererDetails.bounds != oldAxisRendererDetails.bounds ||
        axis.majorGridLines.dashArray?.length != oldWidgetAxis.majorGridLines.dashArray?.length ||
        axis.majorGridLines.width != oldWidgetAxis.majorGridLines.width ||
        axis.majorGridLines.color != oldWidgetAxis.majorGridLines.color ||
        axis.title != oldWidgetAxis.title ||
        axisRendererDetails.visibleRange?.interval != oldAxisRendererDetails.visibleRange?.interval ||
        axisRendererDetails.visibleRange?.minimum != oldAxisRendererDetails.visibleRange?.minimum ||
        axisRendererDetails.visibleRange?.maximum != oldAxisRendererDetails.visibleRange?.maximum ||
        axisRendererDetails.visibleRange?.delta != oldAxisRendererDetails.visibleRange?.delta ||
        axisRendererDetails.isInsideTickPosition != oldAxisRendererDetails.isInsideTickPosition ||
        axis.maximumLabels != oldWidgetAxis.maximumLabels ||
        axis.minorGridLines.dashArray?.length != oldWidgetAxis.minorGridLines.dashArray?.length ||
        axis.minorGridLines.width != oldWidgetAxis.minorGridLines.width ||
        axis.minorGridLines.color != oldWidgetAxis.minorGridLines.color ||
        axis.tickPosition.index != oldWidgetAxis.tickPosition.index) {
      stateProperties.chartAxis.needsRepaint = true;
    } else {
      stateProperties.chartAxis.needsRepaint = false;
    }
  }
}

dynamic getInteractiveTooltipLabel(dynamic value, ChartAxisRenderer axisRenderer) {
  final ChartAxisRendererDetails axisRendererDetails = AxisHelper.getAxisRendererDetails(axisRenderer);
  final ChartAxis axis = axisRendererDetails.axis;
  if (axisRenderer is CategoryAxisRenderer) {
    final CategoryAxisDetails categoryAxisRendererDetails = axisRendererDetails as CategoryAxisDetails;
    value = value < 0 == true ? 0 : value;
    value = categoryAxisRendererDetails.labels[(value.round() >= categoryAxisRendererDetails.labels.length == true
            ? (value.round() > categoryAxisRendererDetails.labels.length == true
                ? categoryAxisRendererDetails.labels.length - 1
                : value - 1)
            : value)
        .round()];
  } else if (axisRenderer is DateTimeCategoryAxisRenderer) {
    final DateTimeCategoryAxisDetails dateTimeAxisDetails = axisRendererDetails as DateTimeCategoryAxisDetails;
    value = value < 0 == true ? 0 : value;
    value = dateTimeAxisDetails.labels[(value.round() >= dateTimeAxisDetails.labels.length == true
            ? (value.round() > dateTimeAxisDetails.labels.length == true
                ? dateTimeAxisDetails.labels.length - 1
                : value - 1)
            : value)
        .round()];
  } else if (axisRenderer is DateTimeAxisRenderer) {
    final DateTimeAxis dateTimeAxis = axisRendererDetails.axis as DateTimeAxis;
    final num interval = axisRendererDetails.visibleRange!.minimum.ceil();
    final num previousInterval = (axisRendererDetails.visibleLabels.isNotEmpty)
        ? axisRendererDetails.visibleLabels[axisRendererDetails.visibleLabels.length - 1].value
        : interval;
    final DateFormat dateFormat =
        dateTimeAxis.dateFormat ?? getDateTimeLabelFormat(axisRenderer, interval.toInt(), previousInterval.toInt());
    value = dateFormat.format(DateTime.fromMillisecondsSinceEpoch(value.toInt()));
  } else {
    value = axis is LogarithmicAxis ? math.pow(10, value) : value;
    value = getLabelValue(value, axis, axis.interactiveTooltip.decimalPlaces);
  }
  return value;
}

Path getMarkerShapesPath(DataMarkerType markerType, Offset position, Size size,
    [SeriesRendererDetails? seriesRendererDetails,
    int? index,
    TrackballBehavior? trackballBehavior,
    Animation<double>? animationController,
    ChartSegment? segment,]) {
  final Path path = Path();
  final Rect rect = Rect.fromLTWH(position.dx - size.width / 2, position.dy - size.height / 2, size.width, size.height);
  switch (markerType) {
    case DataMarkerType.circle:
      {
        getShapesPath(path: path, rect: rect, shapeType: ShapeMarkerType.circle);
      }
      break;
    case DataMarkerType.rectangle:
      {
        getShapesPath(path: path, rect: rect, shapeType: ShapeMarkerType.rectangle);
      }
      break;
    case DataMarkerType.image:
      {
        _loadMarkerImage(seriesRendererDetails!.renderer, trackballBehavior);
      }
      break;
    case DataMarkerType.pentagon:
      {
        getShapesPath(path: path, rect: rect, shapeType: ShapeMarkerType.pentagon);
      }
      break;
    case DataMarkerType.verticalLine:
      {
        getShapesPath(path: path, rect: rect, shapeType: ShapeMarkerType.verticalLine);
      }
      break;
    case DataMarkerType.invertedTriangle:
      {
        getShapesPath(path: path, rect: rect, shapeType: ShapeMarkerType.invertedTriangle);
      }
      break;
    case DataMarkerType.horizontalLine:
      {
        getShapesPath(path: path, rect: rect, shapeType: ShapeMarkerType.horizontalLine);
      }
      break;
    case DataMarkerType.diamond:
      {
        getShapesPath(path: path, rect: rect, shapeType: ShapeMarkerType.diamond);
      }
      break;
    case DataMarkerType.triangle:
      {
        getShapesPath(path: path, rect: rect, shapeType: ShapeMarkerType.triangle);
      }
      break;
    case DataMarkerType.none:
      break;
  }
  return path;
}

class StackingInfo {
  StackingInfo(this.groupName, this.stackingValues);

  String groupName;

  List<double>? stackingValues;
}

void _loadMarkerImage(CartesianSeriesRenderer seriesRenderer, TrackballBehavior? trackballBehavior) async {
  final SeriesRendererDetails seriesRendererDetails = SeriesHelper.getSeriesRendererDetails(seriesRenderer);
  final XyDataSeries<dynamic, dynamic> series = seriesRendererDetails.series as XyDataSeries<dynamic, dynamic>;
  if ((trackballBehavior != null &&
          trackballBehavior.markerSettings != null &&
          trackballBehavior.markerSettings!.shape == DataMarkerType.image &&
          trackballBehavior.markerSettings!.image != null) ||
      ((series.markerSettings.isVisible || seriesRendererDetails.seriesType == 'scatter') &&
          series.markerSettings.shape == DataMarkerType.image &&
          series.markerSettings.image != null)) {
    calculateImage(seriesRendererDetails.stateProperties, seriesRendererDetails, trackballBehavior);
  }
}

ChartLocation getAnnotationLocation(CartesianChartAnnotation annotation, CartesianStateProperties stateProperties) {
  final String? xAxisName = annotation.xAxisName;
  final String? yAxisName = annotation.yAxisName;
  ChartAxisRenderer? xAxisRenderer;
  ChartAxisRenderer? yAxisRenderer;
  num? xValue;
  Rect axisClipRect;
  ChartLocation? location;
  if (annotation.coordinateUnit == CoordinateUnit.logicalPixel) {
    location = annotation.region == AnnotationRegion.chart
        ? ChartLocation(annotation.x.toDouble(), annotation.y.toDouble())
        : ChartLocation(stateProperties.chartAxis.axisClipRect.left + annotation.x,
            stateProperties.chartAxis.axisClipRect.top + annotation.y,);
  } else if (annotation.coordinateUnit == CoordinateUnit.point) {
    for (int i = 0; i < stateProperties.chartAxis.axisRenderersCollection.length; i++) {
      final ChartAxisRenderer axisRenderer = stateProperties.chartAxis.axisRenderersCollection[i];
      final ChartAxisRendererDetails axisRendererDetails = AxisHelper.getAxisRendererDetails(axisRenderer);
      if (xAxisName == axisRendererDetails.name || (xAxisName == null && axisRendererDetails.name == 'primaryXAxis')) {
        xAxisRenderer = axisRenderer;
        if (xAxisRenderer is CategoryAxisRenderer) {
          final CategoryAxisDetails categoryAxisDetails =
              AxisHelper.getAxisRendererDetails(axisRenderer) as CategoryAxisDetails;
          if (annotation.x != null && num.tryParse(annotation.x.toString()) != null) {
            xValue = num.tryParse(annotation.x.toString());
          } else if (annotation.x is String) {
            xValue = categoryAxisDetails.labels.indexOf(annotation.x);
          }
        } else if (xAxisRenderer is DateTimeAxisRenderer) {
          xValue = annotation.x is DateTime ? annotation.x.millisecondsSinceEpoch : annotation.x;
        } else if (xAxisRenderer is DateTimeCategoryAxisRenderer) {
          final DateTimeCategoryAxisDetails dateTimeCategoryAxisDetails =
              AxisHelper.getAxisRendererDetails(xAxisRenderer) as DateTimeCategoryAxisDetails;
          if (annotation.x != null && num.tryParse(annotation.x.toString()) != null) {
            xValue = num.tryParse(annotation.x.toString());
          } else {
            xValue = annotation.x is num
                ? annotation.x
                : (annotation.x is DateTime
                    ? dateTimeCategoryAxisDetails.labels
                        .indexOf(dateTimeCategoryAxisDetails.dateFormat.format(annotation.x))
                    : dateTimeCategoryAxisDetails.labels.indexOf(annotation.x));
          }
        } else {
          xValue = annotation.x is DateTime ? annotation.x.millisecondsSinceEpoch : annotation.x;
        }
      } else if (yAxisName == axisRendererDetails.name ||
          (yAxisName == null && axisRendererDetails.name == 'primaryYAxis')) {
        yAxisRenderer = axisRenderer;
      }
    }

    if (xAxisRenderer != null && yAxisRenderer != null) {
      axisClipRect = calculatePlotOffset(
          stateProperties.chartAxis.axisClipRect,
          Offset(AxisHelper.getAxisRendererDetails(xAxisRenderer).axis.plotOffset,
              AxisHelper.getAxisRendererDetails(yAxisRenderer).axis.plotOffset,),);
      location = calculatePoint(xValue!, annotation.y, AxisHelper.getAxisRendererDetails(xAxisRenderer),
          AxisHelper.getAxisRendererDetails(yAxisRenderer), stateProperties.requireInvertedAxis, null, axisClipRect,);
    }
  } else {
    if (annotation.region == AnnotationRegion.chart) {
      location = ChartLocation(
          stateProperties.renderingDetails.chartContainerRect.left +
              percentageToValue(annotation.x.contains('%') == true ? annotation.x : annotation.x + '%',
                      stateProperties.renderingDetails.chartContainerRect.right,)!
                  .toDouble(),
          stateProperties.renderingDetails.chartContainerRect.top +
              percentageToValue(annotation.y.contains('%') == true ? annotation.y : annotation.y + '%',
                      stateProperties.renderingDetails.chartContainerRect.bottom,)!
                  .toDouble(),);
    } else {
      location = ChartLocation(
          stateProperties.chartAxis.axisClipRect.left +
              percentageToValue(annotation.x.contains('%') == true ? annotation.x : annotation.x + '%',
                      stateProperties.chartAxis.axisClipRect.right - stateProperties.chartAxis.axisClipRect.left,)!
                  .toDouble(),
          stateProperties.chartAxis.axisClipRect.top +
              percentageToValue(annotation.y.contains('%') == true ? annotation.y : annotation.y + '%',
                      stateProperties.chartAxis.axisClipRect.bottom,)!
                  .toDouble(),);
    }
  }
  return location!;
}

void drawTooltipArrowhead(Canvas canvas, Path backgroundPath, Paint fillPaint, Paint strokePaint, double x1, double y1,
    double x2, double y2, double x3, double y3, double x4, double y4,) {
  backgroundPath.moveTo(x1, y1);
  backgroundPath.lineTo(x2, y2);
  backgroundPath.lineTo(x3, y3);
  backgroundPath.lineTo(x4, y4);
  backgroundPath.lineTo(x1, y1);
  fillPaint.isAntiAlias = true;
  canvas.drawPath(backgroundPath, strokePaint);
  canvas.drawPath(backgroundPath, fillPaint);
}

RRect getRoundedCornerRect(Rect rect, double cornerRadius) => RRect.fromRectAndCorners(
      rect,
      bottomLeft: Radius.circular(cornerRadius),
      bottomRight: Radius.circular(cornerRadius),
      topLeft: Radius.circular(cornerRadius),
      topRight: Radius.circular(cornerRadius),
    );

double pointToXVal(CgCartesianChart chart, ChartAxisRenderer axisRenderer, Rect rect, double x, double y) {
  return _coefficientToValue(x / rect.width, axisRenderer);
}

double pointToYVal(CgCartesianChart chart, ChartAxisRenderer axisRenderer, Rect rect, double x, double y) {
  return _coefficientToValue(1 - (y / rect.height), axisRenderer);
}

Rect validateRectXPosition(Rect labelRect, CartesianStateProperties stateProperties) {
  Rect validatedRect = labelRect;
  if (labelRect.right >= stateProperties.chartAxis.axisClipRect.right) {
    validatedRect = Rect.fromLTRB(labelRect.left - (labelRect.right - stateProperties.chartAxis.axisClipRect.right),
        labelRect.top, stateProperties.chartAxis.axisClipRect.right, labelRect.bottom,);
  } else if (labelRect.left <= stateProperties.chartAxis.axisClipRect.left) {
    validatedRect = Rect.fromLTRB(stateProperties.chartAxis.axisClipRect.left, labelRect.top,
        labelRect.right + (stateProperties.chartAxis.axisClipRect.left - labelRect.left), labelRect.bottom,);
  }
  return validatedRect;
}

Rect validateRectYPosition(Rect labelRect, CartesianStateProperties stateProperties) {
  Rect validatedRect = labelRect;
  if (labelRect.bottom >= stateProperties.chartAxis.axisClipRect.bottom) {
    validatedRect = Rect.fromLTRB(
        labelRect.left,
        labelRect.top - (labelRect.bottom - stateProperties.chartAxis.axisClipRect.bottom),
        labelRect.right,
        stateProperties.chartAxis.axisClipRect.bottom,);
  } else if (labelRect.top <= stateProperties.chartAxis.axisClipRect.top) {
    validatedRect = Rect.fromLTRB(labelRect.left, stateProperties.chartAxis.axisClipRect.top, labelRect.right,
        labelRect.bottom + (stateProperties.chartAxis.axisClipRect.top - labelRect.top),);
  }
  return validatedRect;
}

Rect validateRectBounds(Rect tooltipRect, Rect boundary) {
  Rect validatedRect = tooltipRect;
  double difference = 0;

  const double padding = 0.5;

  if (tooltipRect.left < boundary.left) {
    difference = (boundary.left - tooltipRect.left) + padding;
    validatedRect = Rect.fromLTRB(
        validatedRect.left + difference, validatedRect.top, validatedRect.right + difference, validatedRect.bottom,);
  }
  if (tooltipRect.right > boundary.right) {
    difference = (tooltipRect.right - boundary.right) + padding;
    validatedRect = Rect.fromLTRB(
        validatedRect.left - difference, validatedRect.top, validatedRect.right - difference, validatedRect.bottom,);
  }
  if (tooltipRect.top < boundary.top) {
    difference = (boundary.top - tooltipRect.top) + padding;
    validatedRect = Rect.fromLTRB(
        validatedRect.left, validatedRect.top + difference, validatedRect.right, validatedRect.bottom + difference,);
  }

  if (tooltipRect.bottom > boundary.bottom) {
    difference = (tooltipRect.bottom - boundary.bottom) + padding;
    validatedRect = Rect.fromLTRB(
        validatedRect.left, validatedRect.top - difference, validatedRect.right, validatedRect.bottom - difference,);
  }

  return validatedRect;
}

CartesianSeriesRenderer getPreviousSeriesRenderer(
    List<CartesianSeriesRenderer> seriesRendererCollection, num seriesIndex,) {
  for (int i = 0; i < seriesRendererCollection.length; i++) {
    if (seriesIndex == seriesRendererCollection.indexOf(seriesRendererCollection[i]) && i != 0) {
      return seriesRendererCollection[i - 1];
    }
  }
  return seriesRendererCollection[0];
}

void stackedRectPainter(
    Canvas canvas, dynamic seriesRenderer, CartesianStateProperties stateProperties, PainterKey painterKey,) {
  if (seriesRenderer.seriesRendererDetails.visible! == true) {
    canvas.save();
    Rect clipRect;
    Rect axisClipRect;
    CartesianChartPoint<dynamic> point;
    final XyDataSeries<dynamic, dynamic> series = seriesRenderer.seriesRendererDetails.series;
    final RenderingDetails renderingDetails = stateProperties.renderingDetails;
    final int seriesIndex = painterKey.index;
    final Animation<double> seriesAnimation = seriesRenderer.seriesRendererDetails.seriesAnimation;
    final Animation<double> chartElementAnimation = seriesRenderer.seriesRendererDetails.seriesElementAnimation;
    seriesRenderer.seriesRendererDetails.storeSeriesProperties(stateProperties.chartState, seriesIndex);
    double animationFactor;
    animationFactor = (seriesRenderer.seriesRendererDetails.reAnimate == true ||
            (!(renderingDetails.widgetNeedUpdate || renderingDetails.isLegendToggled)))
        ? seriesAnimation.value
        : 1;

    axisClipRect = calculatePlotOffset(
        stateProperties.chartAxis.axisClipRect,
        Offset(seriesRenderer.seriesRendererDetails.xAxisDetails?.axis?.plotOffset,
            seriesRenderer.seriesRendererDetails.yAxisDetails?.axis?.plotOffset,),);
    canvas.clipRect(axisClipRect);
    int segmentIndex = -1;
    if (seriesRenderer.seriesRendererDetails.visibleDataPoints == null ||
        seriesRenderer.seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) {
      seriesRenderer.seriesRendererDetails.visibleDataPoints = <CartesianChartPoint<dynamic>>[];
    }

    seriesRenderer.seriesRendererDetails.setSeriesProperties(seriesRenderer.seriesRendererDetails);
    for (int pointIndex = 0; pointIndex < seriesRenderer.seriesRendererDetails.dataPoints.length; pointIndex++) {
      point = seriesRenderer.seriesRendererDetails.dataPoints[pointIndex];
      seriesRenderer.seriesRendererDetails
          .calculateRegionData(stateProperties, seriesRenderer, painterKey.index, point, pointIndex);
      if (point.isVisible && !point.isGap) {
        seriesRenderer.seriesRendererDetails.drawSegment(
            canvas, seriesRenderer._createSegments(point, segmentIndex += 1, painterKey.index, animationFactor),);
      }
    }
    clipRect = calculatePlotOffset(
        Rect.fromLTRB(
            stateProperties.chartAxis.axisClipRect.left - series.markerSettings.width,
            stateProperties.chartAxis.axisClipRect.top - series.markerSettings.height,
            stateProperties.chartAxis.axisClipRect.right + series.markerSettings.width,
            stateProperties.chartAxis.axisClipRect.bottom + series.markerSettings.height,),
        Offset(seriesRenderer.seriesRendererDetails.xAxisDetails?.axis?.plotOffset,
            seriesRenderer.seriesRendererDetails.yAxisDetails?.axis?.plotOffset,),);
    canvas.restore();
    if ((series.animationDuration <= 0 ||
            !renderingDetails.initialRender! ||
            animationFactor >= stateProperties.seriesDurationFactor) &&
        (series.markerSettings.isVisible || series.dataLabelSettings.isVisible)) {
      canvas.clipRect(clipRect);
      seriesRenderer.seriesRendererDetails.renderSeriesElements(stateProperties.chart, canvas, chartElementAnimation);
    }
    if (animationFactor >= 1) {
      stateProperties.setPainterKey(painterKey.index, painterKey.name, true);
    }
  }
}

List<num?> naturalSpline(
    List<num> xValues, List<num> yValues, List<num?> yCoeff, int dataCount, SplineType? splineType,) {
  const double a = 6;
  final int count = dataCount;
  int i;
  int k;
  final List<num?> yCoef = List<num?>.filled(count, null);
  double d1;
  double d2;
  double d3;
  double dy1;
  double dy2;
  num p;

  final List<num?> u = List<num?>.filled(count, null);
  if (splineType == SplineType.clamped && xValues.length > 1) {
    u[0] = 0.5;
    final num d0 = (xValues[1] - xValues[0]) / (yValues[1] - yValues[0]);
    final num dn = (xValues[count - 1] - xValues[count - 2]) / (yValues[count - 1] - yValues[count - 2]);
    yCoef[0] = (3 * (yValues[1] - yValues[0]) / (xValues[1] - xValues[0])) - (3 * d0);
    yCoef[count - 1] =
        (3 * dn) - ((3 * (yValues[count - 1] - yValues[count - 2])) / (xValues[count - 1] - xValues[count - 2]));

    if (yCoef[0] == double.infinity || yCoef[0]!.isNaN) {
      yCoef[0] = 0;
    }

    if (yCoef[count - 1] == double.infinity || yCoef[count - 1]!.isNaN) {
      yCoef[count - 1] = 0;
    }
  } else {
    yCoef[0] = u[0] = 0;
    yCoef[count - 1] = 0;
  }

  for (i = 1; i < count - 1; i++) {
    yCoef[i] = 0;
    if ((!yValues[i + 1].isNaN) && (!yValues[i - 1].isNaN) && (!yValues[i].isNaN)) {
      d1 = xValues[i].toDouble() - xValues[i - 1].toDouble();
      d2 = xValues[i + 1].toDouble() - xValues[i - 1].toDouble();
      d3 = xValues[i + 1].toDouble() - xValues[i].toDouble();
      dy1 = yValues[i + 1].toDouble() - yValues[i].toDouble();
      dy2 = yValues[i].toDouble() - yValues[i - 1].toDouble();
      if (xValues[i] == xValues[i - 1] || xValues[i] == xValues[i + 1]) {
        yCoef[i] = 0;
        u[i] = 0;
      } else {
        p = 1 / ((d1 * yCoef[i - 1]!) + (2 * d2));
        yCoef[i] = -p * d3;
        if (u[i - 1] != null) {
          u[i] = p * ((a * ((dy1 / d3) - (dy2 / d1))) - (d1 * u[i - 1]!));
        }
      }
    }
  }

  for (k = count - 2; k >= 0; k--) {
    if (u[k] != null && yCoef[k] != null && yCoef[k + 1] != null) {
      yCoef[k] = (yCoef[k]! * yCoef[k + 1]!) + u[k]!;
    }
  }

  yCoeff.addAll(yCoef);
  return yCoeff;
}

num dateTimeInterval(CartesianSeriesRenderer seriesRenderer) {
  final DateTimeAxis xAxis = SeriesHelper.getSeriesRendererDetails(seriesRenderer).xAxisDetails!.axis as DateTimeAxis;
  final DateTimeIntervalType actualIntervalType = xAxis.intervalType;
  num intervalInMilliseconds;
  if (actualIntervalType == DateTimeIntervalType.years) {
    intervalInMilliseconds = 365 * 24 * 60 * 60 * 1000;
  } else if (actualIntervalType == DateTimeIntervalType.months) {
    intervalInMilliseconds = 30 * 24 * 60 * 60 * 1000;
  } else if (actualIntervalType == DateTimeIntervalType.days) {
    intervalInMilliseconds = 24 * 60 * 60 * 1000;
  } else if (actualIntervalType == DateTimeIntervalType.hours) {
    intervalInMilliseconds = 60 * 60 * 1000;
  } else if (actualIntervalType == DateTimeIntervalType.minutes) {
    intervalInMilliseconds = 60 * 1000;
  } else if (actualIntervalType == DateTimeIntervalType.seconds) {
    intervalInMilliseconds = 1000;
  } else {
    intervalInMilliseconds = 30 * 24 * 60 * 60 * 1000;
  }
  return intervalInMilliseconds;
}

MarkerRenderArgs? triggerMarkerRenderEvent(SeriesRendererDetails seriesRendererDetails, Size size,
    DataMarkerType markerType, int pointIndex, Animation<double>? animationController,
    [ChartSegment? segment,]) {
  MarkerRenderArgs? markerargs;
  final int seriesIndex =
      seriesRendererDetails.stateProperties.chartSeries.visibleSeriesRenderers.indexOf(seriesRendererDetails.renderer);
  final XyDataSeries<dynamic, dynamic> series = seriesRendererDetails.series as XyDataSeries<dynamic, dynamic>;
  final MarkerSettingsRenderer markerSettingsRenderer = seriesRendererDetails.markerSettingsRenderer!;
  markerSettingsRenderer.color = series.markerSettings.color;
  if (seriesRendererDetails.chart.onMarkerRender != null) {
    markerargs = MarkerRenderArgs(
        pointIndex, seriesIndex, seriesRendererDetails.visibleDataPoints![pointIndex].overallDataPointIndex,);
    markerargs.markerHeight = size.height;
    markerargs.markerWidth = size.width;
    markerargs.shape = markerType;
    markerargs.color = markerSettingsRenderer.color;
    markerargs.borderColor = markerSettingsRenderer.borderColor;
    markerargs.borderWidth = markerSettingsRenderer.borderWidth;
    seriesRendererDetails.chart.onMarkerRender!(markerargs);
    size = Size(markerargs.markerWidth, markerargs.markerHeight);
    markerType = markerargs.shape;
    markerSettingsRenderer.color = markerargs.color;
    markerSettingsRenderer.borderColor = markerargs.borderColor;
    markerSettingsRenderer.borderWidth = markerargs.borderWidth;
  }
  return markerargs;
}

List<Offset> calculateControlPoints(
    List<num> xValues, List<num?> yValues, double yCoef, double nextyCoef, int i, List<Offset> controlPoints,) {
  final List<double?> values = List<double?>.filled(4, null);
  final double x = xValues[i].toDouble();
  final double y = yValues[i]!.toDouble();
  final double nextX = xValues[i + 1].toDouble();
  final double nextY = yValues[i + 1]!.toDouble();
  const double oneThird = 1 / 3.0;
  double deltaX2 = nextX - x;
  deltaX2 = deltaX2 * deltaX2;
  final double dx1 = (2 * x) + nextX;
  final double dx2 = x + (2 * nextX);
  final double dy1 = (2 * y) + nextY;
  final double dy2 = y + (2 * nextY);
  final double y1 = oneThird * (dy1 - (oneThird * deltaX2 * (yCoef + (0.5 * nextyCoef))));
  final double y2 = oneThird * (dy2 - (oneThird * deltaX2 * ((0.5 * yCoef) + nextyCoef)));
  values[0] = dx1 * oneThird;
  values[1] = y1;
  values[2] = dx2 * oneThird;
  values[3] = y2;
  controlPoints.add(Offset(values[0]!, values[1]!));
  controlPoints.add(Offset(values[2]!, values[3]!));
  return controlPoints;
}

ChartAxisRenderer? getOldAxisRenderer(ChartAxisRenderer axisRenderer, List<ChartAxisRenderer> oldAxisRendererList) {
  for (int i = 0; i < oldAxisRendererList.length; i++) {
    if (AxisHelper.getAxisRendererDetails(oldAxisRendererList[i]).name ==
        AxisHelper.getAxisRendererDetails(axisRenderer).name) {
      return oldAxisRendererList[i];
    }
  }
  return null;
}

CartesianChartPoint<dynamic>? getChartPoint(CartesianSeriesRenderer seriesRenderer, dynamic data, int pointIndex) {
  dynamic xVal;
  dynamic yVal;
  dynamic highVal;
  dynamic lowVal;
  dynamic openVal;
  dynamic closeVal;
  dynamic volumeVal;
  dynamic sortVal;
  dynamic sizeVal;
  dynamic colorVal;
  dynamic textVal;
  dynamic minimumVal;
  dynamic maximumVal;
  bool? isIntermediateSum;
  bool? isTotalSum;
  CartesianChartPoint<dynamic>? currentPoint;
  final SeriesRendererDetails seriesRendererDetails = SeriesHelper.getSeriesRendererDetails(seriesRenderer);
  final dynamic series = seriesRendererDetails.series;
  final ChartIndexedValueMapper<dynamic>? xMap = series.xValueMapper;
  final ChartIndexedValueMapper<dynamic>? yMap = series.yValueMapper;
  final ChartIndexedValueMapper<num>? highMap = series.highValueMapper;
  final ChartIndexedValueMapper<num>? lowMap = series.lowValueMapper;
  final ChartIndexedValueMapper<bool>? isIntermediateSumMap = series.intermediateSumPredicate;
  final ChartIndexedValueMapper<bool>? isTotalSumMap = series.totalSumPredicate;
  final ChartIndexedValueMapper<dynamic>? sortFieldMap = series.sortFieldValueMapper;
  final ChartIndexedValueMapper<Color>? pointColorMap = series.pointColorMapper;
  final dynamic sizeMap = series.sizeValueMapper;
  final ChartIndexedValueMapper<String>? pointTextMap = series.dataLabelMapper;

  if (xMap != null) {
    xVal = xMap(pointIndex);
  }

  if (yMap != null) {
    yVal = yMap(pointIndex);
  }

  if (xVal != null) {
    if (yVal != null) {
      assert(
          yVal.runtimeType == num ||
              yVal.runtimeType == double ||
              yVal.runtimeType == int ||
              yVal.runtimeType.toString() == 'List<num?>' ||
              yVal.runtimeType.toString() == 'List<num>' ||
              yVal.runtimeType.toString() == 'List<double?>' ||
              yVal.runtimeType.toString() == 'List<double>' ||
              yVal.runtimeType.toString() == 'List<int?>' ||
              yVal.runtimeType.toString() == 'List<int>',
          'The Y value will accept only number or list of numbers.',);
    }
    if (highMap != null) {
      highVal = highMap(pointIndex);
    }

    if (lowMap != null) {
      lowVal = lowMap(pointIndex);
    }

    if (sortFieldMap != null) {
      sortVal = sortFieldMap(pointIndex);
    }

    if (sizeMap != null) {
      sizeVal = sizeMap(pointIndex);
    }

    if (pointColorMap != null) {
      colorVal = pointColorMap(pointIndex);
    }

    if (pointTextMap != null) {
      textVal = pointTextMap(pointIndex);
    }

    if (isIntermediateSumMap != null) {
      isIntermediateSum = isIntermediateSumMap(pointIndex);
      isIntermediateSum ??= false;
    } else {
      isIntermediateSum = false;
    }

    if (isTotalSumMap != null) {
      isTotalSum = isTotalSumMap(pointIndex);
      isTotalSum ??= false;
    } else {
      isTotalSum = false;
    }
    currentPoint = CartesianChartPoint<dynamic>(xVal, yVal, textVal, colorVal, sizeVal, highVal, lowVal, openVal,
        closeVal, volumeVal, sortVal, minimumVal, maximumVal, isIntermediateSum, isTotalSum,);
  }
  return currentPoint;
}

num findMinValue(num axisValue, num pointValue) => math.min(axisValue, pointValue);

num findMaxValue(num axisValue, num pointValue) => math.max(axisValue, pointValue);

bool findChangesInPoint(CartesianChartPoint<dynamic> point, CartesianChartPoint<dynamic> oldPoint,
    SeriesRendererDetails seriesRendererDetails,) {
  if (seriesRendererDetails.series.sortingOrder == seriesRendererDetails.oldSeries?.sortingOrder) {
    return point.x != oldPoint.x ||
        point.y != oldPoint.y ||
        point.bubbleSize != oldPoint.bubbleSize ||
        point.sortValue != oldPoint.sortValue;
  } else {
    return true;
  }
}

VisibleRange calculateYRangeOnZoomX(VisibleRange actualRange, dynamic axisRenderer) {
  num? mini;
  num? maxi;
  final dynamic axis = axisRenderer.axis;
  final List<CartesianSeriesRenderer> seriesRenderers = axisRenderer.seriesRenderers;
  final num? minimum = axis.minimum;
  final num? maximum = axis.maximum;
  for (int i = 0; i < seriesRenderers.length && seriesRenderers.isNotEmpty; i++) {
    final SeriesRendererDetails seriesRendererDetails = SeriesHelper.getSeriesRendererDetails(seriesRenderers[i]);
    final dynamic xAxisRenderer = seriesRendererDetails.xAxisDetails;
    xAxisRenderer.calculateRangeAndInterval(axisRenderer.stateProperties, 'AnchoringRange');
    final VisibleRange xRange = xAxisRenderer.visibleRange;
    if (seriesRendererDetails.yAxisDetails == axisRenderer &&
        seriesRendererDetails.visible! == true) {
      final List<CartesianChartPoint<dynamic>> dataPoints = getSampledData(seriesRendererDetails);
      for (int j = 0; j < dataPoints.length; j++) {
        final CartesianChartPoint<dynamic> point = dataPoints[j];
        if (point.xValue >= xRange.minimum == true && point.xValue <= xRange.maximum == true) {
          if (point.cumulativeValue != null || point.yValue != null) {
            final num yValue = point.cumulativeValue != null ? point.cumulativeValue! : point.yValue;
            mini = min(mini ?? yValue, yValue);
            maxi = max(maxi ?? yValue, yValue);
          } else if (point.high != null && point.low != null) {
            mini = min(mini ?? point.low, point.low);
            maxi = max(maxi ?? point.high, point.high);
          }
        }
      }
    }
  }
  return VisibleRange(minimum ?? (mini ?? actualRange.minimum), maximum ?? (maxi ?? actualRange.maximum));
}

bool needCalculateYRange(
    num? minimum, num? maximum, CartesianStateProperties stateProperties, AxisOrientation orientation,) {
  final CgCartesianChart chart = stateProperties.chart;
  return !(minimum != null && maximum != null) &&
      (stateProperties.rangeChangeBySlider ||
          (((stateProperties.zoomedState ?? false) ||
                  stateProperties.zoomProgress ||
                  stateProperties.chart.indicators.isNotEmpty) &&
              (!stateProperties.requireInvertedAxis
                  ? (orientation == AxisOrientation.vertical && chart.zoomPanBehavior.zoomMode == ZoomMode.x)
                  : (orientation == AxisOrientation.horizontal && chart.zoomPanBehavior.zoomMode == ZoomMode.y))));
}

ChartAxisRenderer? findExistingAxisRenderer(ChartAxis axis, List<ChartAxisRenderer> axisRenderers) {
  for (final ChartAxisRenderer axisRenderer in axisRenderers) {
    if (identical(axis, AxisHelper.getAxisRendererDetails(axisRenderer).axis)) {
      return axisRenderer;
    }
  }
  return null;
}

int getOldSegmentIndex(ChartSegment segment) {
  final SegmentProperties segmentProperties = SegmentHelper.getSegmentProperties(segment);
  if (segmentProperties.oldSeriesRenderer != null) {
    for (final ChartSegment oldSegment
        in SeriesHelper.getSeriesRendererDetails(segmentProperties.oldSeriesRenderer!).segments) {
      final SegmentProperties oldSegmentProperties = SegmentHelper.getSegmentProperties(oldSegment);
      if (segment.runtimeType == oldSegment.runtimeType &&
          (SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer).xAxisDetails is CategoryAxisRenderer
              ? oldSegmentProperties.currentPoint!.x == segmentProperties.currentPoint!.x
              : oldSegmentProperties.currentPoint!.xValue == segmentProperties.currentPoint!.xValue)) {
        final SeriesRendererDetails rendererDetails =
            SeriesHelper.getSeriesRendererDetails(segmentProperties.oldSeriesRenderer!);
        return rendererDetails.segments.indexOf(oldSegment);
      }
    }
  }
  return -1;
}

void setAnimationStatus(CartesianStateProperties stateProperties) {
  if (stateProperties.totalAnimatingSeries == stateProperties.animationCompleteCount) {
    stateProperties.renderingDetails.animateCompleted = true;
    stateProperties.animationCompleteCount = 0;
  } else {
    stateProperties.renderingDetails.animateCompleted = false;
  }
  if (stateProperties.renderDataLabel != null) {
    if (stateProperties.renderDataLabel!.state!.mounted == true) {
      stateProperties.renderDataLabel!.state?.render();
    }
  }
}

int calculateDateTimeNiceInterval(ChartAxisRenderer axisRenderer, Size size, VisibleRange range,
    [DateTime? startDate, DateTime? endDate,]) {
  final ChartAxis axis = AxisHelper.getAxisRendererDetails(axisRenderer).axis;
  DateTime? visibleMinimum;
  DateTime? visibleMaximum;
  DateTimeIntervalType? actualIntervalType;
  if (axis is DateTimeAxis) {
    visibleMinimum = axis.visibleMinimum;
    visibleMaximum = axis.visibleMaximum;
  } else if (axis is DateTimeCategoryAxis) {
    visibleMinimum = axis.visibleMinimum;
    visibleMaximum = axis.visibleMaximum;
  }
  final bool notDoubleInterval = (visibleMinimum == null || visibleMaximum == null) ||
      (axis.interval != null && axis.interval! % 1 == 0) ||
      (axis.interval == null);
  const int perDay = 24 * 60 * 60 * 1000;
  startDate ??= DateTime.fromMillisecondsSinceEpoch(range.minimum.toInt());
  endDate ??= DateTime.fromMillisecondsSinceEpoch(range.maximum.toInt());
  num? interval;
  const num hours = 24;
  const num minutes = 60;
  const num seconds = 60;
  const num milliseconds = 1000;
  final num totalDays = ((startDate.millisecondsSinceEpoch - endDate.millisecondsSinceEpoch) / perDay).abs();
  dynamic axisRendererDetails;
  if (axis is DateTimeAxis) {
    axisRendererDetails = AxisHelper.getAxisRendererDetails(axisRenderer) as DateTimeAxisDetails;
    actualIntervalType = axis.intervalType;
  } else if (axis is DateTimeCategoryAxis) {
    axisRendererDetails = AxisHelper.getAxisRendererDetails(axisRenderer) as DateTimeCategoryAxisDetails;
    actualIntervalType = axis.intervalType;
  }
  switch (actualIntervalType) {
    case DateTimeIntervalType.years:
      interval = axisRendererDetails.calculateNumericNiceInterval(axisRenderer, totalDays / 365, size);
      break;
    case DateTimeIntervalType.months:
      interval = axisRendererDetails.calculateNumericNiceInterval(axisRenderer, totalDays / 30, size);
      break;
    case DateTimeIntervalType.days:
      interval = axisRendererDetails.calculateNumericNiceInterval(axisRenderer, totalDays, size);
      break;
    case DateTimeIntervalType.hours:
      interval = axisRendererDetails.calculateNumericNiceInterval(axisRenderer, totalDays * hours, size);
      break;
    case DateTimeIntervalType.minutes:
      interval = axisRendererDetails.calculateNumericNiceInterval(axisRenderer, totalDays * hours * minutes, size);
      break;
    case DateTimeIntervalType.seconds:
      interval =
          axisRendererDetails.calculateNumericNiceInterval(axisRenderer, totalDays * hours * minutes * seconds, size);
      break;
    case DateTimeIntervalType.milliseconds:
      interval = axisRendererDetails.calculateNumericNiceInterval(
          axisRenderer, totalDays * hours * minutes * seconds * milliseconds, size,);
      break;
    case DateTimeIntervalType.auto:
      interval = axisRendererDetails.calculateNumericNiceInterval(axisRenderer, totalDays / 365, size);
      if (interval! >= 1) {
        _setActualIntervalType(axisRenderer, DateTimeIntervalType.years);
        return interval.floor();
      }

      interval = axisRendererDetails.calculateNumericNiceInterval(axisRenderer, totalDays / 30, size);
      if (interval! >= 1) {
        _setActualIntervalType(
            axisRenderer, notDoubleInterval ? DateTimeIntervalType.months : DateTimeIntervalType.years,);
        return interval.floor();
      }

      interval = axisRendererDetails.calculateNumericNiceInterval(axisRenderer, totalDays, size);
      if (interval! >= 1) {
        _setActualIntervalType(
            axisRenderer, notDoubleInterval ? DateTimeIntervalType.days : DateTimeIntervalType.months,);
        return interval.floor();
      }

      interval = axisRendererDetails.calculateNumericNiceInterval(axisRenderer, totalDays * 24, size);
      if (interval! >= 1) {
        _setActualIntervalType(
            axisRenderer, notDoubleInterval ? DateTimeIntervalType.hours : DateTimeIntervalType.days,);
        return interval.floor();
      }

      interval = axisRendererDetails.calculateNumericNiceInterval(axisRenderer, totalDays * 24 * 60, size);
      if (interval! >= 1) {
        _setActualIntervalType(
            axisRenderer, notDoubleInterval ? DateTimeIntervalType.minutes : DateTimeIntervalType.hours,);
        return interval.floor();
      }

      interval = axisRendererDetails.calculateNumericNiceInterval(axisRenderer, totalDays * 24 * 60 * 60, size);
      if (interval! >= 1) {
        _setActualIntervalType(
            axisRenderer, notDoubleInterval ? DateTimeIntervalType.seconds : DateTimeIntervalType.minutes,);
        return interval.floor();
      }

      interval = axisRendererDetails.calculateNumericNiceInterval(axisRenderer, totalDays * 24 * 60 * 60 * 1000, size);
      _setActualIntervalType(
          axisRenderer, notDoubleInterval ? DateTimeIntervalType.milliseconds : DateTimeIntervalType.seconds,);
      return interval! < 1 ? interval.ceil() : interval.floor();
    default:
      break;
  }
  _setActualIntervalType(axisRenderer, actualIntervalType!);
  return interval! < 1 ? interval.ceil() : interval.floor();
}

void _setActualIntervalType(ChartAxisRenderer axisRenderer, DateTimeIntervalType intervalType) {
  if (axisRenderer is DateTimeAxisRenderer) {
    final DateTimeAxisDetails dateTimeAxisDetails =
        AxisHelper.getAxisRendererDetails(axisRenderer) as DateTimeAxisDetails;
    dateTimeAxisDetails.actualIntervalType = intervalType;
  } else if (axisRenderer is DateTimeCategoryAxisRenderer) {
    final DateTimeCategoryAxisDetails dateTimeCategoryAxisDetails =
        AxisHelper.getAxisRendererDetails(axisRenderer) as DateTimeCategoryAxisDetails;
    dateTimeCategoryAxisDetails.actualIntervalType = intervalType;
  }
}

DateFormat getDateTimeLabelFormat(ChartAxisRenderer axisRenderer, [int? interval, int? prevInterval]) {
  DateFormat? format;
  final ChartAxisRendererDetails axisRendererDetails = AxisHelper.getAxisRendererDetails(axisRenderer);
  final bool notDoubleInterval =
      (axisRendererDetails.axis.interval != null && axisRendererDetails.axis.interval! % 1 == 0) ||
          axisRendererDetails.axis.interval == null;
  DateTimeIntervalType? actualIntervalType;
  VisibleRange? visibleRange;
  num? minimum;
  if (axisRenderer is DateTimeAxisRenderer) {
    final DateTimeAxisDetails dateTimeAxisDetails =
        AxisHelper.getAxisRendererDetails(axisRenderer) as DateTimeAxisDetails;
    actualIntervalType = dateTimeAxisDetails.actualIntervalType;
    visibleRange = dateTimeAxisDetails.visibleRange;
    minimum = dateTimeAxisDetails.min;
  } else if (axisRenderer is DateTimeCategoryAxisRenderer) {
    final DateTimeCategoryAxisDetails dateTimeCategoryAxisDetails =
        AxisHelper.getAxisRendererDetails(axisRenderer) as DateTimeCategoryAxisDetails;
    visibleRange = dateTimeCategoryAxisDetails.visibleRange;
    minimum = dateTimeCategoryAxisDetails.min;
    actualIntervalType = dateTimeCategoryAxisDetails.actualIntervalType;
  }
  switch (actualIntervalType) {
    case DateTimeIntervalType.years:
      format = notDoubleInterval ? DateFormat.y() : DateFormat.MMMd();
      break;
    case DateTimeIntervalType.months:
      format = (minimum == interval || interval == prevInterval)
          ? _getFirstLabelFormat(actualIntervalType)
          : _getDateTimeFormat(actualIntervalType, visibleRange, interval, prevInterval);
      break;

    case DateTimeIntervalType.days:
      format = (minimum == interval || interval == prevInterval)
          ? _getFirstLabelFormat(actualIntervalType)
          : _getDateTimeFormat(actualIntervalType, visibleRange, interval, prevInterval);
      break;
    case DateTimeIntervalType.hours:
      format = DateFormat.j();
      break;
    case DateTimeIntervalType.minutes:
      format = DateFormat.Hm();
      break;
    case DateTimeIntervalType.seconds:
      format = DateFormat.ms();
      break;
    case DateTimeIntervalType.milliseconds:
      final DateFormat dateFormat = DateFormat('ss.SSS');
      format = dateFormat;
      break;
    case DateTimeIntervalType.auto:
      break;
    default:
      break;
  }
  return format!;
}

DateFormat? _getDateTimeFormat(
    DateTimeIntervalType? actualIntervalType, VisibleRange? visibleRange, int? interval, int? prevInterval,) {
  final DateTime minimum = DateTime.fromMillisecondsSinceEpoch(interval!);
  final DateTime maximum = DateTime.fromMillisecondsSinceEpoch(prevInterval!);
  DateFormat? format;
  final bool isIntervalDecimal = visibleRange!.interval % 1 == 0;
  if (actualIntervalType == DateTimeIntervalType.months) {
    format = minimum.year == maximum.year
        ? (isIntervalDecimal ? DateFormat.MMM() : DateFormat.MMMd())
        : DateFormat('yyy MMM');
  } else if (actualIntervalType == DateTimeIntervalType.days) {
    format =
        minimum.month != maximum.month ? (isIntervalDecimal ? DateFormat.MMMd() : DateFormat.MEd()) : DateFormat.d();
  }

  return format;
}

DateFormat? _getFirstLabelFormat(DateTimeIntervalType? actualIntervalType) {
  DateFormat? format;

  if (actualIntervalType == DateTimeIntervalType.months) {
    format = DateFormat('yyy MMM');
  } else if (actualIntervalType == DateTimeIntervalType.days) {
    format = DateFormat.MMMd();
  } else if (actualIntervalType == DateTimeIntervalType.minutes) {
    format = DateFormat.Hm();
  }

  return format;
}

void setCategoryMinMaxValues(ChartAxisRenderer axisRenderer, bool isXVisibleRange, bool isYVisibleRange,
    CartesianChartPoint<dynamic> point, int pointIndex, int dataLength, SeriesRendererDetails seriesRendererDetails,) {
  final ChartAxisRendererDetails axisRendererDetails = AxisHelper.getAxisRendererDetails(axisRenderer);
  final String seriesType = seriesRendererDetails.seriesType;
  final bool anchorRangeToVisiblePoints = seriesRendererDetails.yAxisDetails!.axis.anchorRangeToVisiblePoints;
  if (isYVisibleRange) {
    seriesRendererDetails.minimumX ??= point.xValue;
    seriesRendererDetails.maximumX ??= point.xValue;
  }
  if ((isXVisibleRange || !anchorRangeToVisiblePoints) &&
      !seriesType.contains('range') &&
      !seriesType.contains('hilo') &&
      !seriesType.contains('candle') &&
      seriesType != 'boxandwhisker' &&
      seriesType != 'waterfall') {
    seriesRendererDetails.minimumY ??= point.yValue;
    seriesRendererDetails.maximumY ??= point.yValue;
  }
  if (isYVisibleRange && point.xValue != null) {
    seriesRendererDetails.minimumX = math.min(seriesRendererDetails.minimumX!, point.xValue);
    seriesRendererDetails.maximumX = math.max(seriesRendererDetails.maximumX!, point.xValue);
  }
  if (isXVisibleRange || !anchorRangeToVisiblePoints) {
    if (point.yValue != null &&
        (!seriesType.contains('range') &&
            !seriesType.contains('hilo') &&
            !seriesType.contains('candle') &&
            seriesType != 'boxandwhisker' &&
            seriesType != 'waterfall')) {
      seriesRendererDetails.minimumY = math.min(seriesRendererDetails.minimumY!, point.yValue);
      seriesRendererDetails.maximumY = math.max(seriesRendererDetails.maximumY!, point.yValue);
    }

    if (point.high != null) {
      axisRendererDetails.highMin = findMinValue(axisRendererDetails.highMin ?? point.high, point.high);
      axisRendererDetails.highMax = findMaxValue(axisRendererDetails.highMax ?? point.high, point.high);
    }
    if (point.low != null) {
      axisRendererDetails.lowMin = findMinValue(axisRendererDetails.lowMin ?? point.low, point.low);
      axisRendererDetails.lowMax = findMaxValue(axisRendererDetails.lowMax ?? point.low, point.low);
    }
    if (point.maximum != null) {
      axisRendererDetails.highMin = findMinValue(axisRendererDetails.highMin ?? point.maximum!, point.maximum!);
      axisRendererDetails.highMax = findMaxValue(axisRendererDetails.highMax ?? point.minimum!, point.maximum!);
    }
    if (point.minimum != null) {
      axisRendererDetails.lowMin = findMinValue(axisRendererDetails.lowMin ?? point.minimum!, point.minimum!);
      axisRendererDetails.lowMax = findMaxValue(axisRendererDetails.lowMax ?? point.minimum!, point.minimum!);
    }
    if (seriesType == 'waterfall') {
      point.yValue ??= 0;
      seriesRendererDetails.minimumY = findMinValue(seriesRendererDetails.minimumY ?? point.yValue, point.yValue);
      seriesRendererDetails.maximumY = findMaxValue(seriesRendererDetails.maximumY ?? point.maxYValue, point.maxYValue);
    } else if (seriesType == 'errorbar') {
      updateErrorBarAxisRange(seriesRendererDetails, point);
    }
  }

  if (pointIndex >= dataLength - 1) {
    if (seriesType.contains('range') ||
        seriesType.contains('hilo') ||
        seriesType.contains('candle') ||
        seriesType == 'boxandwhisker') {
      axisRendererDetails.lowMin ??= 0;
      axisRendererDetails.lowMax ??= 5;
      axisRendererDetails.highMin ??= 0;
      axisRendererDetails.highMax ??= 5;
      seriesRendererDetails.minimumY = math.min(axisRendererDetails.lowMin!, axisRendererDetails.highMin!);
      seriesRendererDetails.maximumY = math.max(axisRendererDetails.lowMax!, axisRendererDetails.highMax!);
    }
    seriesRendererDetails.minimumY ??= 0;
    seriesRendererDetails.maximumY ??= 5;
  }
}

void calculateDateTimeVisibleRange(Size availableSize, ChartAxisRenderer axisRenderer) {
  final ChartAxisRendererDetails axisRendererDetails = AxisHelper.getAxisRendererDetails(axisRenderer);
  final VisibleRange actualRange = axisRendererDetails.actualRange!;
  final CartesianStateProperties stateProperties = axisRendererDetails.stateProperties;
  axisRendererDetails.setOldRangeFromRangeController();
  axisRendererDetails.visibleRange = stateProperties.rangeChangeBySlider &&
          axisRendererDetails.rangeMinimum != null &&
          axisRendererDetails.rangeMaximum != null
      ? VisibleRange(axisRendererDetails.rangeMinimum, axisRendererDetails.rangeMaximum)
      : VisibleRange(actualRange.minimum, actualRange.maximum);
  final VisibleRange visibleRange = axisRendererDetails.visibleRange!;
  visibleRange.delta = actualRange.delta;
  visibleRange.interval = actualRange.interval;
  bool canAutoScroll = false;
  if (axisRendererDetails.axis.autoScrollingDelta != null &&
      axisRendererDetails.axis.autoScrollingDelta! > 0 &&
      !stateProperties.isRedrawByZoomPan) {
    canAutoScroll = true;
    if (axisRenderer is DateTimeAxisRenderer) {
      final DateTimeAxisDetails dateTimeAxisDetails =
          AxisHelper.getAxisRendererDetails(axisRenderer) as DateTimeAxisDetails;
      dateTimeAxisDetails.updateScrollingDelta();
    } else {
      final DateTimeCategoryAxisDetails dateTimeCategoryAxisDetails =
          AxisHelper.getAxisRendererDetails(axisRenderer) as DateTimeCategoryAxisDetails;
      dateTimeCategoryAxisDetails.updateAutoScrollingDelta(
          dateTimeCategoryAxisDetails.axis.autoScrollingDelta!, axisRenderer,);
    }
  }
  if ((!canAutoScroll || (stateProperties.zoomedState ?? false)) &&
      !(stateProperties.rangeChangeBySlider && !stateProperties.canSetRangeController)) {
    axisRendererDetails.setZoomFactorAndPosition(axisRenderer, stateProperties.zoomedAxisRendererStates);
  }
  if (axisRendererDetails.zoomFactor < 1 ||
      axisRendererDetails.zoomPosition > 0 ||
      (axisRendererDetails.axis.rangeController != null && !stateProperties.renderingDetails.initialRender!)) {
    stateProperties.zoomProgress = true;
    axisRendererDetails.calculateZoomRange(axisRenderer, availableSize);
    visibleRange.interval = axisRendererDetails.axis.enableAutoIntervalOnZooming &&
            stateProperties.zoomProgress &&
            !canAutoScroll &&
            (axisRenderer is DateTimeAxisRenderer || axisRenderer is DateTimeCategoryAxisRenderer)
        ? axisRenderer.calculateInterval(visibleRange, availableSize)
        : visibleRange.interval;
    if (axisRenderer is DateTimeAxisRenderer) {
      visibleRange.minimum = visibleRange.minimum.floor();
      visibleRange.maximum = visibleRange.maximum.floor();
    }
    if (axisRendererDetails.axis.rangeController != null &&
        stateProperties.isRedrawByZoomPan &&
        stateProperties.canSetRangeController &&
        stateProperties.zoomProgress) {
      stateProperties.rangeChangedByChart = true;
      axisRendererDetails.setRangeControllerValues(axisRenderer);
    }
  }
  axisRendererDetails.setZoomValuesFromRangeController();
}

num? getCrossesAtValue(CartesianSeriesRenderer seriesRenderer, CartesianStateProperties stateProperties) {
  num? crossesAt;
  final int seriesIndex = stateProperties.chartSeries.visibleSeriesRenderers.indexOf(seriesRenderer);
  final List<ChartAxisRenderer> axisCollection = stateProperties.requireInvertedAxis
      ? stateProperties.chartAxis.verticalAxisRenderers
      : stateProperties.chartAxis.horizontalAxisRenderers;
  for (int i = 0; i < axisCollection.length; i++) {
    final ChartAxisRendererDetails axisRendererDetails = AxisHelper.getAxisRendererDetails(axisCollection[i]);
    if (SeriesHelper.getSeriesRendererDetails(stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex])
            .xAxisDetails!
            .name ==
        axisRendererDetails.name) {
      crossesAt = axisRendererDetails.crossValue;
      break;
    }
  }
  return crossesAt;
}

List<Offset?> getTooltipPaddingData(SeriesRendererDetails seriesRendererDetails, bool isTrendLine, Rect region,
    Rect paddedRegion, Offset? tooltipPosition,) {
  Offset? padding;
  Offset? position;
  if (seriesRendererDetails.seriesType == 'bubble' && !isTrendLine) {
    padding = Offset(region.center.dx - region.centerLeft.dx, 2 * (region.center.dy - region.topCenter.dy));
    position = Offset(tooltipPosition!.dx, paddedRegion.top);
    if (region.top < 0) {
      padding = Offset(padding.dx, padding.dy + region.top);
    }
  } else if (seriesRendererDetails.seriesType == 'scatter') {
    padding = Offset(
        seriesRendererDetails.series.markerSettings.width, seriesRendererDetails.series.markerSettings.height / 2,);
    position = Offset(tooltipPosition!.dx, tooltipPosition.dy);
  } else if (seriesRendererDetails.seriesType.contains('rangearea') == true) {
    padding = Offset(
        seriesRendererDetails.series.markerSettings.width, seriesRendererDetails.series.markerSettings.height / 2,);
    position = Offset(tooltipPosition!.dx, tooltipPosition.dy);
  } else {
    padding = (seriesRendererDetails.series.markerSettings.isVisible == true)
        ? Offset(
            seriesRendererDetails.series.markerSettings.width / 2,
            seriesRendererDetails.series.markerSettings.height / 2 +
                seriesRendererDetails.series.markerSettings.borderWidth / 2,)
        : const Offset(2, 2);
  }
  return <Offset?>[padding, position ?? tooltipPosition];
}

CartesianSeriesRenderer? getOldSeriesRenderer(CartesianStateProperties stateProperties,
    SeriesRendererDetails seriesRendererDetails, int seriesIndex, List<CartesianSeriesRenderer> oldSeriesRenderers,) {
  if (stateProperties.renderingDetails.widgetNeedUpdate &&
      seriesRendererDetails.xAxisDetails!.zoomFactor == 1 &&
      seriesRendererDetails.yAxisDetails!.zoomFactor == 1 &&
      oldSeriesRenderers.isNotEmpty &&
      oldSeriesRenderers.length - 1 >= seriesIndex &&
      SeriesHelper.getSeriesRendererDetails(oldSeriesRenderers[seriesIndex]).seriesName ==
          seriesRendererDetails.seriesName) {
    return oldSeriesRenderers[seriesIndex];
  } else {
    return null;
  }
}

CartesianChartPoint<dynamic>? getOldChartPoint(
    CartesianStateProperties stateProperties,
    SeriesRendererDetails seriesRendererDetails,
    Type segmentType,
    int seriesIndex,
    int pointIndex,
    CartesianSeriesRenderer? oldSeriesRenderer,
    List<CartesianSeriesRenderer> oldSeriesRenderers,) {
  final RenderingDetails renderingDetails = stateProperties.renderingDetails;
  return seriesRendererDetails.reAnimate == false &&
          (seriesRendererDetails.series.animationDuration > 0 &&
              renderingDetails.widgetNeedUpdate &&
              !renderingDetails.isLegendToggled &&
              oldSeriesRenderers.isNotEmpty &&
              oldSeriesRenderer != null &&
              SeriesHelper.getSeriesRendererDetails(oldSeriesRenderer).segments.isNotEmpty == true &&
              SeriesHelper.getSeriesRendererDetails(oldSeriesRenderer).segments[0].runtimeType == segmentType &&
              oldSeriesRenderers.length - 1 >= seriesIndex &&
              SeriesHelper.getSeriesRendererDetails(oldSeriesRenderer).dataPoints.length - 1 >= pointIndex)
      ? SeriesHelper.getSeriesRendererDetails(oldSeriesRenderer).dataPoints[pointIndex]
      : null;
}

bool shouldShowAxisTooltip(CartesianStateProperties stateProperties) {
  bool requireAxisTooltip = false;
  for (int i = 0; i < stateProperties.chartAxis.axisRenderersCollection.length; i++) {
    final ChartAxisRendererDetails axisRendererDetails =
        AxisHelper.getAxisRendererDetails(stateProperties.chartAxis.axisRenderersCollection[i]);
    requireAxisTooltip =
        axisRendererDetails.axis.maximumLabelWidth != null || axisRendererDetails.axis.labelsExtent != null;
    if (axisRendererDetails.axis.multiLevelLabels != null) {
      requireAxisTooltip = axisRendererDetails.visibleAxisMultiLevelLabels.isNotEmpty;
    }
    if (requireAxisTooltip) {
      break;
    }
  }
  return requireAxisTooltip;
}

int? getVisibleDataPointIndex(int? pointIndex, SeriesRendererDetails seriesRendererDetails) {
  int? index;
  final List<CartesianChartPoint<dynamic>> dataPoints = getSampledData(seriesRendererDetails);
  if (pointIndex != null) {
    if (pointIndex < dataPoints[0].overallDataPointIndex! ||
        pointIndex > dataPoints[dataPoints.length - 1].overallDataPointIndex!) {
      index = null;
    } else if (pointIndex > dataPoints.length - 1) {
      for (int i = 0; i < dataPoints.length; i++) {
        if (pointIndex == dataPoints[i].overallDataPointIndex) {
          index = dataPoints[i].visiblePointIndex;
        }
      }
    } else {
      index = dataPoints[pointIndex].visiblePointIndex;
    }
  }
  return index;
}

bool isLineTypeSeries(String seriesType) {
  return seriesType == 'line' ||
      seriesType == 'spline' ||
      seriesType == 'stepline' ||
      seriesType == 'stackedline' ||
      seriesType == 'stackedline100';
}

class CircularIntervalList<T> {
  CircularIntervalList(this._values);

  final List<T> _values;
  int _index = 0;

  T get next {
    if (_index >= _values.length) {
      _index = 0;
    }
    return _values[_index++];
  }
}

double? getRSquaredValue(CartesianSeriesRenderer series, Trendline trendline, List<double>? slope, double? intercept) {
  double rSquare = 0;
  const int power = 2;
  final List<int> xValue = <int>[];
  final List<double> yValue = <double>[];
  final SeriesRendererDetails seriesRendererDetails = SeriesHelper.getSeriesRendererDetails(series);
  double yMean = 0;
  const int startXValue = 1;
  final DateTime excelDate = DateTime(1900);
  final List<CartesianChartPoint<dynamic>> dataPoints = getSampledData(seriesRendererDetails);
  for (int i = 0; i < dataPoints.length; i++) {
    xValue.add(seriesRendererDetails.xAxisDetails?.axisRenderer is DateTimeAxisRenderer
        ? dataPoints[i].x.difference(excelDate).inDays
        : i + startXValue,);
    yValue.add(dataPoints[i].y.toDouble());
    yMean += dataPoints[i].y.toDouble();
  }
  yMean = yMean / yValue.length;
  double ssTot = 0;
  for (int j = 0; j < yValue.length; j++) {
    ssTot += math.pow(yValue[j] - yMean, power);
  }
  double ssReg = 0;
  if (trendline.type == TrendlineType.linear) {
    for (int k = 0; k < yValue.length; k++) {
      ssReg += math.pow(((slope![0] * xValue[k]) + intercept!) - yMean, power);
    }
  }
  if (trendline.type == TrendlineType.exponential) {
    for (int k = 0; k < yValue.length; k++) {
      ssReg += math.pow((intercept! * math.exp(slope![0] * xValue[k])) - yMean, power);
    }
  }
  if (trendline.type == TrendlineType.logarithmic) {
    for (int k = 0; k < yValue.length; k++) {
      ssReg += math.pow(((slope![0] * math.log(xValue[k])) + intercept!) - yMean, power);
    }
  }
  if (trendline.type == TrendlineType.power) {
    for (int k = 0; k < yValue.length; k++) {
      ssReg += math.pow((intercept! * math.pow(xValue[k], slope![0])) - yMean, power);
    }
  }
  if (trendline.type == TrendlineType.polynomial) {
    for (int k = 0; k < yValue.length; k++) {
      double yCap = 0;
      for (int i = 0; i < slope!.length; i++) {
        yCap += slope[i] * math.pow(xValue[k], i);
      }
      ssReg += math.pow(yCap - yMean, power);
    }
  }
  rSquare = ssReg / ssTot;
  return rSquare.isNaN ? 0 : rSquare;
}

Offset calculatePointToPixel(CartesianChartPoint<dynamic> point, dynamic seriesRenderer) {
  final num x = point.x;
  final num y = point.y;
  final SeriesRendererDetails seriesRendererDetails;
  if (seriesRenderer is SeriesRendererDetails == false) {
    seriesRendererDetails = SeriesHelper.getSeriesRendererDetails(seriesRenderer);
  } else {
    seriesRendererDetails = seriesRenderer;
  }
  final ChartAxisRendererDetails xAxisDetails = seriesRendererDetails.xAxisDetails!;
  final ChartAxisRendererDetails yAxisDetails = seriesRendererDetails.yAxisDetails!;

  final bool isInverted = seriesRendererDetails.stateProperties.requireInvertedAxis;

  final CartesianSeries<dynamic, dynamic> series = seriesRendererDetails.series;
  final ChartLocation location = calculatePoint(x, y, xAxisDetails, yAxisDetails, isInverted, series,
      seriesRendererDetails.stateProperties.chartAxis.axisClipRect,);

  return Offset(location.x, location.y);
}

CartesianChartPoint<dynamic> calculatePixelToPoint(Offset position, dynamic seriesRenderer) {
  SeriesRendererDetails seriesRendererDetails;
  if (seriesRenderer is SeriesRendererDetails) {
    seriesRendererDetails = seriesRenderer;
  } else {
    seriesRendererDetails = SeriesHelper.getSeriesRendererDetails(seriesRenderer);
  }
  if (seriesRendererDetails.xAxisDetails == null) {
    seriesRendererDetails =
        SeriesHelper.getSeriesRendererDetails(seriesRendererDetails.stateProperties.oldSeriesRenderers[0]);
  }
  ChartAxisRendererDetails xAxisDetails = seriesRendererDetails.xAxisDetails!;
  ChartAxisRendererDetails yAxisDetails = seriesRendererDetails.yAxisDetails!;

  final ChartAxis xAxis = xAxisDetails.axis;
  final ChartAxis yAxis = yAxisDetails.axis;

  final CartesianSeries<dynamic, dynamic> series = seriesRendererDetails.series;

  final Rect rect = seriesRendererDetails.stateProperties.chartAxis.axisClipRect;

  if (series.xAxisName != null || series.yAxisName != null) {
    for (final ChartAxisRenderer axisRenderer
        in seriesRendererDetails.stateProperties.chartAxis.axisRenderersCollection) {
      final ChartAxisRendererDetails axisDetails = AxisHelper.getAxisRendererDetails(axisRenderer);
      if (axisDetails.name == series.xAxisName) {
        xAxisDetails = axisDetails;
      } else if (axisDetails.name == series.yAxisName) {
        yAxisDetails = axisDetails;
      }
    }
  } else {
    xAxisDetails = xAxisDetails;
    yAxisDetails = yAxisDetails;
  }

  num xValue = pointToXValue(seriesRendererDetails.stateProperties.requireInvertedAxis, xAxisDetails.axisRenderer, rect,
      position.dx - (rect.left + xAxis.plotOffset), position.dy - (rect.top + yAxis.plotOffset),);
  num yValue = pointToYValue(seriesRendererDetails.stateProperties.requireInvertedAxis, yAxisDetails.axisRenderer, rect,
      position.dx - (rect.left + xAxis.plotOffset), position.dy - (rect.top + yAxis.plotOffset),);

  if (xAxisDetails is LogarithmicAxisDetails) {
    final LogarithmicAxis axis = xAxis as LogarithmicAxis;
    xValue = math.pow(xValue, calculateLogBaseValue(xValue, axis.logBase));
  } else {
    xValue = xValue;
  }
  if (yAxisDetails is LogarithmicAxisDetails) {
    final LogarithmicAxis axis = yAxis as LogarithmicAxis;
    yValue = math.pow(yValue, calculateLogBaseValue(yValue, axis.logBase));
  } else {
    yValue = yValue;
  }
  return CartesianChartPoint<dynamic>(xValue, yValue);
}

String getSeriesType(CartesianSeriesRenderer seriesRenderer) {
  String seriesType = '';

  if (seriesRenderer is AreaSeriesRenderer) {
    seriesType = 'area';
  } else if (seriesRenderer is ColumnSeriesRenderer) {
    seriesType = 'column';
  } else if (seriesRenderer is LineSeriesRenderer) {
    seriesType = 'line';
  }

  return seriesType;
}
